| /* |
| * Copyright (C) 2017 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 <ctype.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <pthread.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <string> |
| |
| #include <android-base/file.h> |
| #include <android-base/macros.h> |
| #include <android-base/scopeguard.h> |
| #include <android-base/stringprintf.h> |
| #include <log/log_event_list.h> |
| #include <log/log_properties.h> |
| #include <log/log_read.h> |
| #include <private/android_filesystem_config.h> |
| |
| #include "LogStatistics.h" |
| #include "LogTags.h" |
| #include "LogUtils.h" |
| |
| using android::base::make_scope_guard; |
| |
| static LogTags* logtags; |
| |
| const char LogTags::system_event_log_tags[] = "/system/etc/event-log-tags"; |
| const char LogTags::dynamic_event_log_tags[] = "/dev/event-log-tags"; |
| // Only for debug |
| const char LogTags::debug_event_log_tags[] = "/data/misc/logd/event-log-tags"; |
| |
| // Sniff for first uid=%d in utf8z comment string |
| static uid_t sniffUid(const char* comment, const char* endp) { |
| if (!comment) return AID_ROOT; |
| |
| if (*comment == '#') ++comment; |
| while ((comment < endp) && (*comment != '\n') && isspace(*comment)) |
| ++comment; |
| static const char uid_str[] = "uid="; |
| if (((comment + strlen(uid_str)) >= endp) || |
| fastcmp<strncmp>(comment, uid_str, strlen(uid_str)) || |
| !isdigit(comment[strlen(uid_str)])) |
| return AID_ROOT; |
| char* cp; |
| unsigned long Uid = strtoul(comment + 4, &cp, 10); |
| if ((cp > endp) || (Uid >= INT_MAX)) return AID_ROOT; |
| |
| return Uid; |
| } |
| |
| // Checks for file corruption, and report false if there was no need |
| // to rebuild the referenced file. Failure to rebuild is only logged, |
| // does not cause a return value of false. |
| bool LogTags::RebuildFileEventLogTags(const char* filename, bool warn) { |
| int fd; |
| |
| { |
| android::RWLock::AutoRLock readLock(rwlock); |
| |
| if (tag2total.begin() == tag2total.end()) { |
| return false; |
| } |
| |
| file2watermark_const_iterator iwater = file2watermark.find(filename); |
| if (iwater == file2watermark.end()) { |
| return false; |
| } |
| |
| struct stat sb; |
| if (!stat(filename, &sb) && ((size_t)sb.st_size >= iwater->second)) { |
| return false; |
| } |
| |
| // dump what we already know back into the file? |
| fd = TEMP_FAILURE_RETRY(open( |
| filename, O_WRONLY | O_TRUNC | O_CLOEXEC | O_NOFOLLOW | O_BINARY)); |
| if (fd >= 0) { |
| time_t now = time(nullptr); |
| struct tm tm; |
| localtime_r(&now, &tm); |
| char timebuf[20]; |
| strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", &tm); |
| android::base::WriteStringToFd( |
| android::base::StringPrintf( |
| "# Rebuilt %.20s, content owned by logd\n", timebuf), |
| fd); |
| for (const auto& it : tag2total) { |
| android::base::WriteStringToFd( |
| formatEntry_locked(it.first, AID_ROOT), fd); |
| } |
| close(fd); |
| } |
| } |
| |
| if (warn) { |
| android::prdebug( |
| ((fd < 0) ? "%s failed to rebuild" |
| : "%s missing, damaged or truncated; rebuilt"), |
| filename); |
| } |
| |
| if (fd >= 0) { |
| android::RWLock::AutoWLock writeLock(rwlock); |
| |
| struct stat sb; |
| if (!stat(filename, &sb)) file2watermark[filename] = sb.st_size; |
| } |
| |
| return true; |
| } |
| |
| void LogTags::AddEventLogTags(uint32_t tag, uid_t uid, const std::string& Name, |
| const std::string& Format, const char* source, |
| bool warn) { |
| std::string Key = Name; |
| if (Format.length()) Key += "+" + Format; |
| |
| bool update = !source || !!strcmp(source, system_event_log_tags); |
| bool newOne; |
| |
| { |
| android::RWLock::AutoWLock writeLock(rwlock); |
| |
| tag2total_const_iterator itot = tag2total.find(tag); |
| |
| // unlikely except for dupes, or updates to uid list (more later) |
| if (itot != tag2total.end()) update = false; |
| |
| newOne = tag2name.find(tag) == tag2name.end(); |
| key2tag[Key] = tag; |
| |
| if (Format.length()) { |
| if (key2tag.find(Name) == key2tag.end()) { |
| key2tag[Name] = tag; |
| } |
| tag2format[tag] = Format; |
| } |
| tag2name[tag] = Name; |
| |
| tag2uid_const_iterator ut = tag2uid.find(tag); |
| if (ut != tag2uid.end()) { |
| if (uid == AID_ROOT) { |
| tag2uid.erase(ut); |
| update = true; |
| } else if (ut->second.find(uid) == ut->second.end()) { |
| const_cast<uid_list&>(ut->second).emplace(uid); |
| update = true; |
| } |
| } else if (newOne && (uid != AID_ROOT)) { |
| tag2uid[tag].emplace(uid); |
| update = true; |
| } |
| |
| // updatePersist -> trigger output on modified |
| // content, reset tag2total if available |
| if (update && (itot != tag2total.end())) tag2total[tag] = 0; |
| } |
| |
| if (update) { |
| WritePersistEventLogTags(tag, uid, source); |
| } else if (warn && !newOne && source) { |
| // For the files, we want to report dupes. |
| android::prdebug("Multiple tag %" PRIu32 " %s %s %s", tag, Name.c_str(), |
| Format.c_str(), source); |
| } |
| } |
| |
| // Read the event log tags file, and build up our internal database |
| void LogTags::ReadFileEventLogTags(const char* filename, bool warn) { |
| bool etc = !strcmp(filename, system_event_log_tags); |
| |
| if (!etc) { |
| RebuildFileEventLogTags(filename, warn); |
| } |
| std::string content; |
| if (android::base::ReadFileToString(filename, &content)) { |
| char* cp = (char*)content.c_str(); |
| char* endp = cp + content.length(); |
| |
| { |
| android::RWLock::AutoRLock writeLock(rwlock); |
| |
| file2watermark[filename] = content.length(); |
| } |
| |
| char* lineStart = cp; |
| while (cp < endp) { |
| if (*cp == '\n') { |
| lineStart = cp; |
| } else if (lineStart) { |
| if (*cp == '#') { |
| /* comment; just scan to end */ |
| lineStart = nullptr; |
| } else if (isdigit(*cp)) { |
| unsigned long Tag = strtoul(cp, &cp, 10); |
| if (warn && (Tag > emptyTag)) { |
| android::prdebug("tag too large %lu", Tag); |
| } |
| while ((cp < endp) && (*cp != '\n') && isspace(*cp)) ++cp; |
| if (cp >= endp) break; |
| if (*cp == '\n') continue; |
| const char* name = cp; |
| /* Determine whether it is a valid tag name [a-zA-Z0-9_] */ |
| bool hasAlpha = false; |
| while ((cp < endp) && (isalnum(*cp) || (*cp == '_'))) { |
| if (!isdigit(*cp)) hasAlpha = true; |
| ++cp; |
| } |
| std::string Name(name, cp - name); |
| #ifdef ALLOW_NOISY_LOGGING_OF_PROBLEM_WITH_LOTS_OF_TECHNICAL_DEBT |
| static const size_t maximum_official_tag_name_size = 24; |
| if (warn && |
| (Name.length() > maximum_official_tag_name_size)) { |
| android::prdebug("tag name too long %s", Name.c_str()); |
| } |
| #endif |
| if (hasAlpha && |
| ((cp >= endp) || (*cp == '#') || isspace(*cp))) { |
| if (Tag > emptyTag) { |
| if (*cp != '\n') lineStart = nullptr; |
| continue; |
| } |
| while ((cp < endp) && (*cp != '\n') && isspace(*cp)) |
| ++cp; |
| const char* format = cp; |
| uid_t uid = AID_ROOT; |
| while ((cp < endp) && (*cp != '\n')) { |
| if (*cp == '#') { |
| uid = sniffUid(cp, endp); |
| lineStart = nullptr; |
| break; |
| } |
| ++cp; |
| } |
| while ((cp > format) && isspace(cp[-1])) { |
| --cp; |
| lineStart = nullptr; |
| } |
| std::string Format(format, cp - format); |
| |
| AddEventLogTags((uint32_t)Tag, uid, Name, Format, |
| filename, warn); |
| } else { |
| if (warn) { |
| android::prdebug("tag name invalid %.*s", |
| (int)(cp - name + 1), name); |
| } |
| lineStart = nullptr; |
| } |
| } else if (!isspace(*cp)) { |
| break; |
| } |
| } |
| cp++; |
| } |
| } else if (warn) { |
| android::prdebug("Cannot read %s", filename); |
| } |
| } |
| |
| // Extract a 4-byte value from a byte stream. |
| static inline uint32_t get4LE(const char* msg) { |
| const uint8_t* src = reinterpret_cast<const uint8_t*>(msg); |
| return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24); |
| } |
| |
| // Additional persistent sources for invented log tags. Read the |
| // special pmsg event for log tags, and build up our internal |
| // database with any found. |
| void LogTags::ReadPersistEventLogTags() { |
| struct logger_list* logger_list = |
| android_logger_list_alloc(ANDROID_LOG_PSTORE | ANDROID_LOG_NONBLOCK, 0, (pid_t)0); |
| if (!logger_list) return; |
| |
| struct logger* e = android_logger_open(logger_list, LOG_ID_EVENTS); |
| struct logger* s = android_logger_open(logger_list, LOG_ID_SECURITY); |
| if (!e && !s) { |
| android_logger_list_free(logger_list); |
| return; |
| } |
| |
| for (;;) { |
| struct log_msg log_msg; |
| int ret = android_logger_list_read(logger_list, &log_msg); |
| if (ret <= 0) break; |
| |
| const char* msg = log_msg.msg(); |
| if (!msg) continue; |
| if (log_msg.entry.len <= sizeof(uint32_t)) continue; |
| uint32_t Tag = get4LE(msg); |
| if (Tag != TAG_DEF_LOG_TAG) continue; |
| uid_t uid = log_msg.entry.uid; |
| |
| std::string Name; |
| std::string Format; |
| android_log_list_element elem; |
| { |
| auto ctx = create_android_log_parser(log_msg.msg() + sizeof(uint32_t), |
| log_msg.entry.len - sizeof(uint32_t)); |
| auto guard = make_scope_guard([&ctx]() { android_log_destroy(&ctx); }); |
| elem = android_log_read_next(ctx); |
| if (elem.type != EVENT_TYPE_LIST) { |
| continue; |
| } |
| elem = android_log_read_next(ctx); |
| if (elem.type != EVENT_TYPE_INT) { |
| continue; |
| } |
| Tag = elem.data.int32; |
| elem = android_log_read_next(ctx); |
| if (elem.type != EVENT_TYPE_STRING) { |
| continue; |
| } |
| Name = std::string(elem.data.string, elem.len); |
| elem = android_log_read_next(ctx); |
| if (elem.type != EVENT_TYPE_STRING) { |
| continue; |
| } |
| Format = std::string(elem.data.string, elem.len); |
| elem = android_log_read_next(ctx); |
| } |
| if ((elem.type != EVENT_TYPE_LIST_STOP) || !elem.complete) continue; |
| |
| AddEventLogTags(Tag, uid, Name, Format); |
| } |
| android_logger_list_free(logger_list); |
| } |
| |
| LogTags::LogTags() { |
| ReadFileEventLogTags(system_event_log_tags); |
| // Following will likely fail on boot, but is required if logd restarts |
| ReadFileEventLogTags(dynamic_event_log_tags, false); |
| if (__android_log_is_debuggable()) { |
| ReadFileEventLogTags(debug_event_log_tags, false); |
| } |
| ReadPersistEventLogTags(); |
| |
| logtags = this; |
| } |
| |
| // Converts an event tag into a name |
| const char* LogTags::tagToName(uint32_t tag) const { |
| tag2name_const_iterator it; |
| |
| android::RWLock::AutoRLock readLock(const_cast<android::RWLock&>(rwlock)); |
| |
| it = tag2name.find(tag); |
| if ((it == tag2name.end()) || (it->second.length() == 0)) return nullptr; |
| |
| return it->second.c_str(); |
| } |
| |
| // Prototype in LogUtils.h allowing external access to our database. |
| // |
| // This must be a pure reader to our database, as everything else is |
| // guaranteed single-threaded except this access point which is |
| // asynchonous and can be multithreaded and thus rentrant. The |
| // object's rwlock is only used to guarantee atomic access to the |
| // unordered_map to prevent corruption, with a requirement to be a |
| // low chance of contention for this call. If we end up changing |
| // this algorithm resulting in write, then we should use a different |
| // lock than the object's rwlock to protect groups of associated |
| // actions. |
| const char* android::tagToName(uint32_t tag) { |
| LogTags* me = logtags; |
| |
| if (!me) return nullptr; |
| me->WritePmsgEventLogTags(tag); |
| return me->tagToName(tag); |
| } |
| |
| // converts an event tag into a format |
| const char* LogTags::tagToFormat(uint32_t tag) const { |
| tag2format_const_iterator iform; |
| |
| android::RWLock::AutoRLock readLock(const_cast<android::RWLock&>(rwlock)); |
| |
| iform = tag2format.find(tag); |
| if (iform == tag2format.end()) return nullptr; |
| |
| return iform->second.c_str(); |
| } |
| |
| // converts a name into an event tag |
| uint32_t LogTags::nameToTag(const char* name) const { |
| uint32_t ret = emptyTag; |
| |
| // Bug: Only works for a single entry, we can have multiple entries, |
| // one for each format, so we find first entry recorded, or entry with |
| // no format associated with it. |
| |
| android::RWLock::AutoRLock readLock(const_cast<android::RWLock&>(rwlock)); |
| |
| key2tag_const_iterator ik = key2tag.find(std::string(name)); |
| if (ik != key2tag.end()) ret = ik->second; |
| |
| return ret; |
| } |
| |
| // Caller must perform locks, can be under reader (for pre-check) or |
| // writer lock. We use this call to invent a new deterministically |
| // random tag, unique is cleared if no conflicts. If format is NULL, |
| // we are in readonly mode. |
| uint32_t LogTags::nameToTag_locked(const std::string& name, const char* format, |
| bool& unique) { |
| key2tag_const_iterator ik; |
| |
| bool write = format != nullptr; |
| unique = write; |
| |
| if (!write) { |
| // Bug: Only works for a single entry, we can have multiple entries, |
| // one for each format, so we find first entry recorded, or entry with |
| // no format associated with it. |
| ik = key2tag.find(name); |
| if (ik == key2tag.end()) return emptyTag; |
| return ik->second; |
| } |
| |
| std::string Key(name); |
| if (*format) Key += std::string("+") + format; |
| |
| ik = key2tag.find(Key); |
| if (ik != key2tag.end()) { |
| unique = false; |
| return ik->second; |
| } |
| |
| size_t Hash = key2tag.hash_function()(Key); |
| uint32_t Tag = Hash; |
| // This sets an upper limit on the conflics we are allowed to deal with. |
| for (unsigned i = 0; i < 256;) { |
| tag2name_const_iterator it = tag2name.find(Tag); |
| if (it == tag2name.end()) return Tag; |
| std::string localKey(it->second); |
| tag2format_const_iterator iform = tag2format.find(Tag); |
| if ((iform == tag2format.end()) && iform->second.length()) { |
| localKey += "+" + iform->second; |
| } |
| unique = !!it->second.compare(localKey); |
| if (!unique) return Tag; // unlikely except in a race |
| |
| ++i; |
| // Algorithm to convert hash to next tag |
| if (i < 32) { |
| Tag = (Hash >> i); |
| // size_t is 32 bits, or upper word zero, rotate |
| if ((sizeof(Hash) <= 4) || ((Hash & (uint64_t(-1LL) << 32)) == 0)) { |
| Tag |= Hash << (32 - i); |
| } |
| } else { |
| Tag = Hash + i - 31; |
| } |
| } |
| return emptyTag; |
| } |
| |
| static int openFile(const char* name, int mode, bool warning) { |
| int fd = TEMP_FAILURE_RETRY(open(name, mode)); |
| if ((fd < 0) && warning) { |
| android::prdebug("Failed open %s (%d)", name, errno); |
| } |
| return fd; |
| } |
| |
| void LogTags::WritePmsgEventLogTags(uint32_t tag, uid_t uid) { |
| android::RWLock::AutoRLock readLock(rwlock); |
| |
| tag2total_const_iterator itot = tag2total.find(tag); |
| if (itot == tag2total.end()) return; // source is a static entry |
| |
| size_t lastTotal = itot->second; |
| |
| // Every 16K (half the smallest configurable pmsg buffer size) record |
| static const size_t rate_to_pmsg = 16 * 1024; |
| if (lastTotal && (LogStatistics::sizesTotal() - lastTotal) < rate_to_pmsg) { |
| return; |
| } |
| |
| static int pmsg_fd = -1; |
| if (pmsg_fd < 0) { |
| pmsg_fd = TEMP_FAILURE_RETRY(open("/dev/pmsg0", O_WRONLY | O_CLOEXEC)); |
| // unlikely, but deal with partners with borken pmsg |
| if (pmsg_fd < 0) return; |
| } |
| |
| std::string Name = tag2name[tag]; |
| tag2format_const_iterator iform = tag2format.find(tag); |
| std::string Format = (iform != tag2format.end()) ? iform->second : ""; |
| |
| auto ctx = create_android_logger(TAG_DEF_LOG_TAG); |
| auto guard = make_scope_guard([&ctx]() { android_log_destroy(&ctx); }); |
| if (android_log_write_int32(ctx, static_cast<int32_t>(tag) < 0) || |
| android_log_write_string8_len(ctx, Name.c_str(), Name.size()) < 0 || |
| android_log_write_string8_len(ctx, Format.c_str(), Format.size()) < 0) { |
| return; |
| } |
| |
| const char* cp = nullptr; |
| ssize_t len = android_log_write_list_buffer(ctx, &cp); |
| |
| if (len <= 0 || cp == nullptr) { |
| return; |
| } |
| |
| std::string buffer(cp, len); |
| |
| /* |
| * struct { |
| * // what we provide to pstore |
| * android_pmsg_log_header_t pmsgHeader; |
| * // what we provide to file |
| * android_log_header_t header; |
| * // caller provides |
| * union { |
| * struct { |
| * char prio; |
| * char payload[]; |
| * } string; |
| * struct { |
| * uint32_t tag |
| * char payload[]; |
| * } binary; |
| * }; |
| * }; |
| */ |
| |
| struct timespec ts; |
| clock_gettime(CLOCK_REALTIME, &ts); |
| |
| android_log_header_t header = { |
| .id = LOG_ID_EVENTS, |
| .tid = (uint16_t)gettid(), |
| .realtime.tv_sec = (uint32_t)ts.tv_sec, |
| .realtime.tv_nsec = (uint32_t)ts.tv_nsec, |
| }; |
| |
| uint32_t outTag = TAG_DEF_LOG_TAG; |
| outTag = get4LE((const char*)&outTag); |
| |
| android_pmsg_log_header_t pmsgHeader = { |
| .magic = LOGGER_MAGIC, |
| .len = (uint16_t)(sizeof(pmsgHeader) + sizeof(header) + sizeof(outTag) + |
| buffer.length()), |
| .uid = (uint16_t)AID_ROOT, |
| .pid = (uint16_t)getpid(), |
| }; |
| |
| struct iovec Vec[] = { { (unsigned char*)&pmsgHeader, sizeof(pmsgHeader) }, |
| { (unsigned char*)&header, sizeof(header) }, |
| { (unsigned char*)&outTag, sizeof(outTag) }, |
| { (unsigned char*)const_cast<char*>(buffer.data()), |
| buffer.length() } }; |
| |
| tag2uid_const_iterator ut = tag2uid.find(tag); |
| if (ut == tag2uid.end()) { |
| TEMP_FAILURE_RETRY(writev(pmsg_fd, Vec, arraysize(Vec))); |
| } else if (uid != AID_ROOT) { |
| pmsgHeader.uid = (uint16_t)uid; |
| TEMP_FAILURE_RETRY(writev(pmsg_fd, Vec, arraysize(Vec))); |
| } else { |
| for (auto& it : ut->second) { |
| pmsgHeader.uid = (uint16_t)it; |
| TEMP_FAILURE_RETRY(writev(pmsg_fd, Vec, arraysize(Vec))); |
| } |
| } |
| } |
| |
| void LogTags::WriteDynamicEventLogTags(uint32_t tag, uid_t uid) { |
| static const int mode = |
| O_WRONLY | O_APPEND | O_CLOEXEC | O_NOFOLLOW | O_BINARY; |
| |
| int fd = openFile(dynamic_event_log_tags, mode, true); |
| if (fd < 0) return; |
| |
| android::RWLock::AutoWLock writeLock(rwlock); |
| |
| std::string ret = formatEntry_locked(tag, uid, false); |
| android::base::WriteStringToFd(ret, fd); |
| close(fd); |
| |
| size_t size = 0; |
| file2watermark_const_iterator iwater; |
| |
| iwater = file2watermark.find(dynamic_event_log_tags); |
| if (iwater != file2watermark.end()) size = iwater->second; |
| |
| file2watermark[dynamic_event_log_tags] = size + ret.length(); |
| } |
| |
| void LogTags::WriteDebugEventLogTags(uint32_t tag, uid_t uid) { |
| static const int mode = |
| O_WRONLY | O_APPEND | O_CLOEXEC | O_NOFOLLOW | O_BINARY; |
| |
| static bool one = true; |
| int fd = openFile(debug_event_log_tags, mode, one); |
| one = fd >= 0; |
| if (!one) return; |
| |
| android::RWLock::AutoWLock writeLock(rwlock); |
| |
| std::string ret = formatEntry_locked(tag, uid, false); |
| android::base::WriteStringToFd(ret, fd); |
| close(fd); |
| |
| size_t size = 0; |
| file2watermark_const_iterator iwater; |
| |
| iwater = file2watermark.find(debug_event_log_tags); |
| if (iwater != file2watermark.end()) size = iwater->second; |
| |
| file2watermark[debug_event_log_tags] = size + ret.length(); |
| } |
| |
| // How we maintain some runtime or reboot stickiness |
| void LogTags::WritePersistEventLogTags(uint32_t tag, uid_t uid, |
| const char* source) { |
| // very unlikely |
| bool etc = source && !strcmp(source, system_event_log_tags); |
| if (etc) return; |
| |
| bool dynamic = source && !strcmp(source, dynamic_event_log_tags); |
| bool debug = (!dynamic && source && !strcmp(source, debug_event_log_tags)) || |
| !__android_log_is_debuggable(); |
| |
| WritePmsgEventLogTags(tag, uid); |
| |
| size_t lastTotal = 0; |
| { |
| android::RWLock::AutoRLock readLock(rwlock); |
| |
| tag2total_const_iterator itot = tag2total.find(tag); |
| if (itot != tag2total.end()) lastTotal = itot->second; |
| } |
| |
| if (lastTotal == 0) { // denotes first time for this one |
| if (!dynamic || !RebuildFileEventLogTags(dynamic_event_log_tags)) { |
| WriteDynamicEventLogTags(tag, uid); |
| } |
| |
| if (!debug && !RebuildFileEventLogTags(debug_event_log_tags)) { |
| WriteDebugEventLogTags(tag, uid); |
| } |
| } |
| |
| lastTotal = LogStatistics::sizesTotal(); |
| if (!lastTotal) ++lastTotal; |
| |
| // record totals for next watermark. |
| android::RWLock::AutoWLock writeLock(rwlock); |
| tag2total[tag] = lastTotal; |
| } |
| |
| // nameToTag converts a name into an event tag. If format is NULL, then we |
| // are in readonly mode. |
| uint32_t LogTags::nameToTag(uid_t uid, const char* name, const char* format) { |
| std::string Name = std::string(name); |
| bool write = format != nullptr; |
| bool updateUid = uid != AID_ROOT; |
| bool updateFormat = format && *format; |
| bool unique; |
| uint32_t Tag; |
| |
| { |
| android::RWLock::AutoRLock readLock(rwlock); |
| |
| Tag = nameToTag_locked(Name, format, unique); |
| if (updateUid && (Tag != emptyTag) && !unique) { |
| tag2uid_const_iterator ut = tag2uid.find(Tag); |
| if (updateUid) { |
| if ((ut != tag2uid.end()) && |
| (ut->second.find(uid) == ut->second.end())) { |
| unique = write; // write passthrough to update uid counts |
| if (!write) Tag = emptyTag; // deny read access |
| } |
| } else { |
| unique = write && (ut != tag2uid.end()); |
| } |
| } |
| } |
| |
| if (Tag == emptyTag) return Tag; |
| WritePmsgEventLogTags(Tag, uid); // record references periodically |
| if (!unique) return Tag; |
| |
| bool updateWrite = false; |
| bool updateTag; |
| |
| // Special case of AddEventLogTags, checks per-uid counter which makes |
| // no sense there, and is also optimized somewhat to reduce write times. |
| { |
| android::RWLock::AutoWLock writeLock(rwlock); |
| |
| // double check after switch from read lock to write lock for Tag |
| updateTag = tag2name.find(Tag) == tag2name.end(); |
| // unlikely, either update, race inviting conflict or multiple uids |
| if (!updateTag) { |
| Tag = nameToTag_locked(Name, format, unique); |
| if (Tag == emptyTag) return Tag; |
| // is it multiple uid's setting this value |
| if (!unique) { |
| tag2uid_const_iterator ut = tag2uid.find(Tag); |
| if (updateUid) { |
| // Add it to the uid list |
| if ((ut == tag2uid.end()) || |
| (ut->second.find(uid) != ut->second.end())) { |
| return Tag; |
| } |
| const_cast<uid_list&>(ut->second).emplace(uid); |
| updateWrite = true; |
| } else { |
| if (ut == tag2uid.end()) return Tag; |
| // (system) adding a global one, erase the uid list |
| tag2uid.erase(ut); |
| updateWrite = true; |
| } |
| } |
| } |
| |
| // Update section |
| size_t count; |
| if (updateUid) { |
| count = 0; |
| uid2count_const_iterator ci = uid2count.find(uid); |
| if (ci != uid2count.end()) { |
| count = ci->second; |
| if (count >= max_per_uid) { |
| if (!updateWrite) return emptyTag; |
| // If we are added to the per-Uid perms, leak the Tag |
| // if it already exists. |
| updateUid = false; |
| updateTag = false; |
| updateFormat = false; |
| } |
| } |
| } |
| |
| // updateWrite -> trigger output on modified content, reset tag2total |
| // also sets static to dynamic entries if they are alterred, |
| // only occurs if they have a uid, and runtime adds another uid. |
| if (updateWrite) tag2total[Tag] = 0; |
| |
| if (updateTag) { |
| // mark as a dynamic entry, but do not upset current total counter |
| tag2total_const_iterator itot = tag2total.find(Tag); |
| if (itot == tag2total.end()) tag2total[Tag] = 0; |
| |
| if (*format) { |
| key2tag[Name + "+" + format] = Tag; |
| if (key2tag.find(Name) == key2tag.end()) key2tag[Name] = Tag; |
| } else { |
| key2tag[Name] = Tag; |
| } |
| tag2name[Tag] = Name; |
| } |
| if (updateFormat) tag2format[Tag] = format; |
| |
| if (updateUid) { |
| tag2uid[Tag].emplace(uid); |
| uid2count[uid] = count + 1; |
| } |
| } |
| |
| if (updateTag || updateFormat || updateWrite) { |
| WritePersistEventLogTags(Tag, uid); |
| } |
| |
| return Tag; |
| } |
| |
| std::string LogTags::formatEntry(uint32_t tag, uid_t uid, const char* name, |
| const char* format) { |
| if (!format || !format[0]) { |
| return android::base::StringPrintf("%" PRIu32 "\t%s\n", tag, name); |
| } |
| size_t len = (strlen(name) + 7) / 8; |
| static const char tabs[] = "\t\t\t"; |
| if (len > strlen(tabs)) len = strlen(tabs); |
| std::string Uid; |
| if (uid != AID_ROOT) Uid = android::base::StringPrintf(" # uid=%u", uid); |
| return android::base::StringPrintf("%" PRIu32 "\t%s%s\t%s%s\n", tag, name, |
| &tabs[len], format, Uid.c_str()); |
| } |
| |
| std::string LogTags::formatEntry_locked(uint32_t tag, uid_t uid, |
| bool authenticate) { |
| const char* name = tag2name[tag].c_str(); |
| |
| const char* format = ""; |
| tag2format_const_iterator iform = tag2format.find(tag); |
| if (iform != tag2format.end()) format = iform->second.c_str(); |
| |
| // Access permission test, do not report dynamic entries |
| // that do not belong to us. |
| tag2uid_const_iterator ut = tag2uid.find(tag); |
| if (ut == tag2uid.end()) { |
| return formatEntry(tag, AID_ROOT, name, format); |
| } |
| if (uid != AID_ROOT) { |
| if (authenticate && (ut->second.find(uid) == ut->second.end())) { |
| return std::string(""); |
| } |
| return formatEntry(tag, uid, name, format); |
| } |
| |
| // Show all, one for each registered uid (we are group root) |
| std::string ret; |
| for (auto& it : ut->second) { |
| ret += formatEntry(tag, it, name, format); |
| } |
| return ret; |
| } |
| |
| std::string LogTags::formatEntry(uint32_t tag, uid_t uid) { |
| android::RWLock::AutoRLock readLock(rwlock); |
| return formatEntry_locked(tag, uid); |
| } |
| |
| std::string LogTags::formatGetEventTag(uid_t uid, const char* name, |
| const char* format) { |
| bool all = name && (name[0] == '*') && !name[1]; |
| bool list = !name || all; |
| std::string ret; |
| |
| if (!list) { |
| // switch to read entry only if format == "*" |
| if (format && (format[0] == '*') && !format[1]) format = nullptr; |
| |
| // WAI: for null format, only works for a single entry, we can have |
| // multiple entries, one for each format, so we find first entry |
| // recorded, or entry with no format associated with it. |
| // We may desire to print all that match the name, but we did not |
| // add a mapping table for that and the cost is too high. |
| uint32_t tag = nameToTag(uid, name, format); |
| if (tag == emptyTag) return std::string("-1 ESRCH"); |
| if (uid == AID_ROOT) { |
| android::RWLock::AutoRLock readLock(rwlock); |
| |
| // first uid in list so as to manufacture an accurate reference |
| tag2uid_const_iterator ut = tag2uid.find(tag); |
| if ((ut != tag2uid.end()) && |
| (ut->second.begin() != ut->second.end())) { |
| uid = *(ut->second.begin()); |
| } |
| } |
| ret = formatEntry(tag, uid, name, format ?: tagToFormat(tag)); |
| if (!ret.length()) return std::string("-1 ESRCH"); |
| return ret; |
| } |
| |
| android::RWLock::AutoRLock readLock(rwlock); |
| if (all) { |
| // everything under the sun |
| for (const auto& it : tag2name) { |
| ret += formatEntry_locked(it.first, uid); |
| } |
| } else { |
| // set entries are dynamic |
| for (const auto& it : tag2total) { |
| ret += formatEntry_locked(it.first, uid); |
| } |
| } |
| return ret; |
| } |