blob: 22cf43bf8d6131d4dfb853d98bbe591a122a38f9 [file] [log] [blame]
/*
* Copyright (C) 2007-2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <functional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <log/event_tag_map.h>
#include <log/log_properties.h>
#include <private/android_logger.h>
#include <utils/FastStrcmp.h>
#include <utils/RWLock.h>
#include "log_portability.h"
#include "logd_reader.h"
#define OUT_TAG "EventTagMap"
class MapString {
private:
const std::string* alloc; // HAS-AN
const std::string_view str; // HAS-A
public:
operator const std::string_view() const {
return str;
}
const char* data() const {
return str.data();
}
size_t length() const {
return str.length();
}
bool operator==(const MapString& rval) const {
if (length() != rval.length()) return false;
if (length() == 0) return true;
return fastcmp<strncmp>(data(), rval.data(), length()) == 0;
}
bool operator!=(const MapString& rval) const {
return !(*this == rval);
}
MapString(const char* str, size_t len) : alloc(NULL), str(str, len) {
}
explicit MapString(const std::string& str)
: alloc(new std::string(str)), str(alloc->data(), alloc->length()) {
}
MapString(MapString&& rval) noexcept
: alloc(rval.alloc), str(rval.data(), rval.length()) {
rval.alloc = NULL;
}
explicit MapString(const MapString& rval)
: alloc(rval.alloc ? new std::string(*rval.alloc) : NULL),
str(alloc ? alloc->data() : rval.data(), rval.length()) {
}
~MapString() {
if (alloc) delete alloc;
}
};
// Hash for MapString
template <>
struct std::hash<MapString>
: public std::unary_function<const MapString&, size_t> {
size_t operator()(const MapString& __t) const noexcept {
if (!__t.length()) return 0;
return std::hash<std::string_view>()(std::string_view(__t));
}
};
typedef std::pair<MapString, MapString> TagFmt;
template <>
struct std::hash<TagFmt> : public std::unary_function<const TagFmt&, size_t> {
size_t operator()(const TagFmt& __t) const noexcept {
// Tag is typically unique. Will cost us an extra 100ns for the
// unordered_map lookup if we instead did a hash that combined
// both of tag and fmt members, e.g.:
//
// return std::hash<MapString>()(__t.first) ^
// std::hash<MapString>()(__t.second);
return std::hash<MapString>()(__t.first);
}
};
// Map
struct EventTagMap {
#define NUM_MAPS 2
// memory-mapped source file; we get strings from here
void* mapAddr[NUM_MAPS];
size_t mapLen[NUM_MAPS];
private:
std::unordered_map<uint32_t, TagFmt> Idx2TagFmt;
std::unordered_map<TagFmt, uint32_t> TagFmt2Idx;
std::unordered_map<MapString, uint32_t> Tag2Idx;
// protect unordered sets
android::RWLock rwlock;
public:
EventTagMap() {
memset(mapAddr, 0, sizeof(mapAddr));
memset(mapLen, 0, sizeof(mapLen));
}
~EventTagMap() {
Idx2TagFmt.clear();
TagFmt2Idx.clear();
Tag2Idx.clear();
for (size_t which = 0; which < NUM_MAPS; ++which) {
if (mapAddr[which]) {
munmap(mapAddr[which], mapLen[which]);
mapAddr[which] = 0;
}
}
}
bool emplaceUnique(uint32_t tag, const TagFmt& tagfmt, bool verbose = false);
const TagFmt* find(uint32_t tag) const;
int find(TagFmt&& tagfmt) const;
int find(MapString&& tag) const;
};
bool EventTagMap::emplaceUnique(uint32_t tag, const TagFmt& tagfmt,
bool verbose) {
bool ret = true;
static const char errorFormat[] =
OUT_TAG ": duplicate tag entries %" PRIu32 ":%.*s:%.*s and %" PRIu32
":%.*s:%.*s)\n";
android::RWLock::AutoWLock writeLock(rwlock);
{
std::unordered_map<uint32_t, TagFmt>::const_iterator it;
it = Idx2TagFmt.find(tag);
if (it != Idx2TagFmt.end()) {
if (verbose) {
fprintf(stderr, errorFormat, it->first, (int)it->second.first.length(),
it->second.first.data(), (int)it->second.second.length(),
it->second.second.data(), tag, (int)tagfmt.first.length(),
tagfmt.first.data(), (int)tagfmt.second.length(),
tagfmt.second.data());
}
ret = false;
} else {
Idx2TagFmt.emplace(std::make_pair(tag, tagfmt));
}
}
{
std::unordered_map<TagFmt, uint32_t>::const_iterator it;
it = TagFmt2Idx.find(tagfmt);
if (it != TagFmt2Idx.end()) {
if (verbose) {
fprintf(stderr, errorFormat, it->second, (int)it->first.first.length(),
it->first.first.data(), (int)it->first.second.length(),
it->first.second.data(), tag, (int)tagfmt.first.length(),
tagfmt.first.data(), (int)tagfmt.second.length(),
tagfmt.second.data());
}
ret = false;
} else {
TagFmt2Idx.emplace(std::make_pair(tagfmt, tag));
}
}
{
std::unordered_map<MapString, uint32_t>::const_iterator it;
it = Tag2Idx.find(tagfmt.first);
if (!tagfmt.second.length() && (it != Tag2Idx.end())) {
Tag2Idx.erase(it);
it = Tag2Idx.end();
}
if (it == Tag2Idx.end()) {
Tag2Idx.emplace(std::make_pair(tagfmt.first, tag));
}
}
return ret;
}
const TagFmt* EventTagMap::find(uint32_t tag) const {
std::unordered_map<uint32_t, TagFmt>::const_iterator it;
android::RWLock::AutoRLock readLock(const_cast<android::RWLock&>(rwlock));
it = Idx2TagFmt.find(tag);
if (it == Idx2TagFmt.end()) return NULL;
return &(it->second);
}
int EventTagMap::find(TagFmt&& tagfmt) const {
std::unordered_map<TagFmt, uint32_t>::const_iterator it;
android::RWLock::AutoRLock readLock(const_cast<android::RWLock&>(rwlock));
it = TagFmt2Idx.find(std::move(tagfmt));
if (it == TagFmt2Idx.end()) return -1;
return it->second;
}
int EventTagMap::find(MapString&& tag) const {
std::unordered_map<MapString, uint32_t>::const_iterator it;
android::RWLock::AutoRLock readLock(const_cast<android::RWLock&>(rwlock));
it = Tag2Idx.find(std::move(tag));
if (it == Tag2Idx.end()) return -1;
return it->second;
}
// The position after the end of a valid section of the tag string,
// caller makes sure delimited appropriately.
static const char* endOfTag(const char* cp) {
while (*cp && (isalnum(*cp) || strchr("_.-@,", *cp))) ++cp;
return cp;
}
// Scan one tag line.
//
// "pData" should be pointing to the first digit in the tag number. On
// successful return, it will be pointing to the last character in the
// tag line (i.e. the character before the start of the next line).
//
// lineNum = 0 removes verbose comments and requires us to cache the
// content rather than make direct raw references since the content
// will disappear after the call. A non-zero lineNum means we own the
// data and it will outlive the call.
//
// Returns 0 on success, nonzero on failure.
static int scanTagLine(EventTagMap* map, const char*& pData, int lineNum) {
char* ep;
unsigned long val = strtoul(pData, &ep, 10);
const char* cp = ep;
if (cp == pData) {
if (lineNum) {
fprintf(stderr, OUT_TAG ": malformed tag number on line %d\n", lineNum);
}
errno = EINVAL;
return -1;
}
uint32_t tagIndex = val;
if (tagIndex != val) {
if (lineNum) {
fprintf(stderr, OUT_TAG ": tag number too large on line %d\n", lineNum);
}
errno = ERANGE;
return -1;
}
while ((*++cp != '\n') && isspace(*cp)) {
}
if (*cp == '\n') {
if (lineNum) {
fprintf(stderr, OUT_TAG ": missing tag string on line %d\n", lineNum);
}
errno = EINVAL;
return -1;
}
const char* tag = cp;
cp = endOfTag(cp);
size_t tagLen = cp - tag;
if (!isspace(*cp)) {
if (lineNum) {
fprintf(stderr, OUT_TAG ": invalid tag char %c on line %d\n", *cp,
lineNum);
}
errno = EINVAL;
return -1;
}
while (isspace(*cp) && (*cp != '\n')) ++cp;
const char* fmt = NULL;
size_t fmtLen = 0;
if (*cp && (*cp != '#')) {
fmt = cp;
while (*cp && (*cp != '\n') && (*cp != '#')) ++cp;
while ((cp > fmt) && isspace(*(cp - 1))) --cp;
fmtLen = cp - fmt;
}
// KISS Only report identicals if they are global
// Ideally we want to check if there are identicals
// recorded for the same uid, but recording that
// unused detail in our database is too burdensome.
bool verbose = true;
while (*cp && (*cp != '#') && (*cp != '\n')) ++cp;
if (*cp == '#') {
do {
++cp;
} while (isspace(*cp) && (*cp != '\n'));
verbose = !!fastcmp<strncmp>(cp, "uid=", strlen("uid="));
}
while (*cp && (*cp != '\n')) ++cp;
#ifdef DEBUG
fprintf(stderr, "%d: %p: %.*s\n", lineNum, tag, (int)(cp - pData), pData);
#endif
pData = cp;
if (lineNum) {
if (map->emplaceUnique(tagIndex,
TagFmt(std::make_pair(MapString(tag, tagLen),
MapString(fmt, fmtLen))),
verbose)) {
return 0;
}
} else {
// cache
if (map->emplaceUnique(
tagIndex,
TagFmt(std::make_pair(MapString(std::string(tag, tagLen)),
MapString(std::string(fmt, fmtLen)))))) {
return 0;
}
}
errno = EMLINK;
return -1;
}
static const char* eventTagFiles[NUM_MAPS] = {
EVENT_TAG_MAP_FILE, "/dev/event-log-tags",
};
// Parse the tags out of the file.
static int parseMapLines(EventTagMap* map, size_t which) {
const char* cp = static_cast<char*>(map->mapAddr[which]);
size_t len = map->mapLen[which];
const char* endp = cp + len;
// insist on EOL at EOF; simplifies parsing and null-termination
if (!len || (*(endp - 1) != '\n')) {
#ifdef DEBUG
fprintf(stderr, OUT_TAG ": map file %zu[%zu] missing EOL on last line\n",
which, len);
#endif
if (which) { // do not propagate errors for other files
return 0;
}
errno = EINVAL;
return -1;
}
bool lineStart = true;
int lineNum = 1;
while (cp < endp) {
if (*cp == '\n') {
lineStart = true;
lineNum++;
} else if (lineStart) {
if (*cp == '#') {
// comment; just scan to end
lineStart = false;
} else if (isdigit(*cp)) {
// looks like a tag; scan it out
if (scanTagLine(map, cp, lineNum) != 0) {
if (!which || (errno != EMLINK)) {
return -1;
}
}
lineNum++; // we eat the '\n'
// leave lineStart==true
} else if (isspace(*cp)) {
// looks like leading whitespace; keep scanning
} else {
fprintf(stderr,
OUT_TAG
": unexpected chars (0x%02x) in tag number on line %d\n",
*cp, lineNum);
errno = EINVAL;
return -1;
}
} else {
// this is a blank or comment line
}
cp++;
}
return 0;
}
// Open the map file and allocate a structure to manage it.
//
// We create a private mapping because we want to terminate the log tag
// strings with '\0'.
EventTagMap* android_openEventTagMap(const char* fileName) {
EventTagMap* newTagMap;
off_t end[NUM_MAPS];
int save_errno, fd[NUM_MAPS];
size_t which;
memset(fd, -1, sizeof(fd));
memset(end, 0, sizeof(end));
for (which = 0; which < NUM_MAPS; ++which) {
const char* tagfile = fileName ? fileName : eventTagFiles[which];
fd[which] = open(tagfile, O_RDONLY | O_CLOEXEC);
if (fd[which] < 0) {
if (!which) {
save_errno = errno;
fprintf(stderr, OUT_TAG ": unable to open map '%s': %s\n", tagfile,
strerror(save_errno));
goto fail_errno;
}
continue;
}
end[which] = lseek(fd[which], 0L, SEEK_END);
save_errno = errno;
(void)lseek(fd[which], 0L, SEEK_SET);
if (!which && (end[0] < 0)) {
fprintf(stderr, OUT_TAG ": unable to seek map '%s' %s\n", tagfile,
strerror(save_errno));
goto fail_close;
}
if (fileName) break; // Only allow one as specified
}
newTagMap = new EventTagMap;
if (newTagMap == NULL) {
save_errno = errno;
goto fail_close;
}
for (which = 0; which < NUM_MAPS; ++which) {
if (fd[which] >= 0) {
newTagMap->mapAddr[which] =
mmap(NULL, end[which], which ? PROT_READ : PROT_READ | PROT_WRITE,
which ? MAP_SHARED : MAP_PRIVATE, fd[which], 0);
save_errno = errno;
close(fd[which]); /* fd DONE */
fd[which] = -1;
if ((newTagMap->mapAddr[which] != MAP_FAILED) &&
(newTagMap->mapAddr[which] != NULL)) {
newTagMap->mapLen[which] = end[which];
} else if (!which) {
const char* tagfile = fileName ? fileName : eventTagFiles[which];
fprintf(stderr, OUT_TAG ": mmap(%s) failed: %s\n", tagfile,
strerror(save_errno));
goto fail_unmap;
}
}
}
for (which = 0; which < NUM_MAPS; ++which) {
if (parseMapLines(newTagMap, which) != 0) {
delete newTagMap;
return NULL;
}
/* See 'fd DONE' comments above and below, no need to clean up here */
}
return newTagMap;
fail_unmap:
save_errno = EINVAL;
delete newTagMap;
fail_close:
for (which = 0; which < NUM_MAPS; ++which) close(fd[which]); /* fd DONE */
fail_errno:
errno = save_errno;
return NULL;
}
// Close the map.
void android_closeEventTagMap(EventTagMap* map) {
if (map) delete map;
}
// Cache miss, go to logd to acquire a public reference.
// Because we lack access to a SHARED PUBLIC /dev/event-log-tags file map?
static const TagFmt* __getEventTag(EventTagMap* map, unsigned int tag) {
// call event tag service to arrange for a new tag
char* buf = NULL;
// Can not use android::base::StringPrintf, asprintf + free instead.
static const char command_template[] = "getEventTag id=%u";
int ret = asprintf(&buf, command_template, tag);
if (ret > 0) {
// Add some buffer margin for an estimate of the full return content.
size_t size =
ret - strlen(command_template) +
strlen("65535\n4294967295\t?\t\t\t?\t# uid=32767\n\n\f?success?");
if (size > (size_t)ret) {
char* np = static_cast<char*>(realloc(buf, size));
if (np) {
buf = np;
} else {
size = ret;
}
} else {
size = ret;
}
// Ask event log tag service for an existing entry
if (__send_log_msg(buf, size) >= 0) {
buf[size - 1] = '\0';
char* ep;
unsigned long val = strtoul(buf, &ep, 10); // return size
const char* cp = ep;
if ((buf != cp) && (val > 0) && (*cp == '\n')) { // truncation OK
++cp;
if (!scanTagLine(map, cp, 0)) {
free(buf);
return map->find(tag);
}
}
}
free(buf);
}
return NULL;
}
// Look up an entry in the map.
const char* android_lookupEventTag_len(const EventTagMap* map, size_t* len, unsigned int tag) {
if (len) *len = 0;
const TagFmt* str = map->find(tag);
if (!str) {
str = __getEventTag(const_cast<EventTagMap*>(map), tag);
}
if (!str) return NULL;
if (len) *len = str->first.length();
return str->first.data();
}
// Look up an entry in the map.
const char* android_lookupEventFormat_len(const EventTagMap* map, size_t* len, unsigned int tag) {
if (len) *len = 0;
const TagFmt* str = map->find(tag);
if (!str) {
str = __getEventTag(const_cast<EventTagMap*>(map), tag);
}
if (!str) return NULL;
if (len) *len = str->second.length();
return str->second.data();
}
// This function is deprecated and replaced with android_lookupEventTag_len
// since it will cause the map to change from Shared and backed by a file,
// to Private Dirty and backed up by swap, albeit highly compressible. By
// deprecating this function everywhere, we save 100s of MB of memory space.
const char* android_lookupEventTag(const EventTagMap* map, unsigned int tag) {
size_t len;
const char* tagStr = android_lookupEventTag_len(map, &len, tag);
if (!tagStr) return tagStr;
char* cp = const_cast<char*>(tagStr);
cp += len;
if (*cp) *cp = '\0'; // Trigger copy on write :-( and why deprecated.
return tagStr;
}
// Look up tagname, generate one if necessary, and return a tag
int android_lookupEventTagNum(EventTagMap* map, const char* tagname, const char* format, int prio) {
const char* ep = endOfTag(tagname);
size_t len = ep - tagname;
if (!len || *ep) {
errno = EINVAL;
return -1;
}
if ((prio != ANDROID_LOG_UNKNOWN) && (prio < ANDROID_LOG_SILENT) &&
!__android_log_is_loggable_len(prio, tagname, len,
__android_log_is_debuggable()
? ANDROID_LOG_VERBOSE
: ANDROID_LOG_DEBUG)) {
errno = EPERM;
return -1;
}
if (!format) format = "";
ssize_t fmtLen = strlen(format);
int ret = map->find(TagFmt(
std::make_pair(MapString(tagname, len), MapString(format, fmtLen))));
if (ret != -1) return ret;
// call event tag service to arrange for a new tag
char* buf = NULL;
// Can not use android::base::StringPrintf, asprintf + free instead.
static const char command_template[] = "getEventTag name=%s format=\"%s\"";
ret = asprintf(&buf, command_template, tagname, format);
if (ret > 0) {
// Add some buffer margin for an estimate of the full return content.
char* cp;
size_t size =
ret - strlen(command_template) +
strlen("65535\n4294967295\t?\t\t\t?\t# uid=32767\n\n\f?success?");
if (size > (size_t)ret) {
cp = static_cast<char*>(realloc(buf, size));
if (cp) {
buf = cp;
} else {
size = ret;
}
} else {
size = ret;
}
// Ask event log tag service for an allocation
if (__send_log_msg(buf, size) >= 0) {
buf[size - 1] = '\0';
unsigned long val = strtoul(buf, &cp, 10); // return size
if ((buf != cp) && (val > 0) && (*cp == '\n')) { // truncation OK
val = strtoul(cp + 1, &cp, 10); // allocated tag number
if ((val > 0) && (val < UINT32_MAX) && (*cp == '\t')) {
free(buf);
ret = val;
// cache
map->emplaceUnique(ret, TagFmt(std::make_pair(
MapString(std::string(tagname, len)),
MapString(std::string(format, fmtLen)))));
return ret;
}
}
}
free(buf);
}
// Hail Mary
ret = map->find(MapString(tagname, len));
if (ret == -1) errno = ESRCH;
return ret;
}