logging: update from upstream
Followed instructions from go/nnapi-dep-instructions
Updated OWNERS file for out repo
BUG=b:211342927
TEST=FEATURES=test emerge-amd64-generic nnapi aosp-frameworks-ml-nn
Change-Id: I5186da9d4a96eb765c6fe52105af0ebbce2be56f
diff --git a/OWNERS b/OWNERS
index 7529cb9..7c3fa12 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1 +1,5 @@
-include platform/system/core:/janitors/OWNERS
+jmpollock@google.com
+skyostil@google.com
+jackshen@google.com
+shafron@google.com
+mpolney@google.com
diff --git a/liblog/Android.bp b/liblog/Android.bp
index bf89903..4dd6dd3 100644
--- a/liblog/Android.bp
+++ b/liblog/Android.bp
@@ -158,10 +158,7 @@
"com.android.runtime",
// DO NOT add more apex names here
],
- pgo: {
- sampling: true,
- profile_file: "logging/liblog.profdata",
- },
+ afdo: true,
}
ndk_headers {
diff --git a/liblog/include/log/logprint.h b/liblog/include/log/logprint.h
index 7dfd914..0cff640 100644
--- a/liblog/include/log/logprint.h
+++ b/liblog/include/log/logprint.h
@@ -17,6 +17,7 @@
#pragma once
#include <stdint.h>
+#include <stdio.h>
#include <sys/types.h>
#include <android/log.h>
@@ -147,13 +148,9 @@
size_t* p_outLength);
/**
- * Either print or do not print log line, based on filter
- *
- * Assumes single threaded execution
- *
+ * Formats a log message into a FILE*.
*/
-int android_log_printLogLine(AndroidLogFormat* p_format, int fd,
- const AndroidLogEntry* entry);
+size_t android_log_printLogLine(AndroidLogFormat* p_format, FILE* fp, const AndroidLogEntry* entry);
#ifdef __cplusplus
}
diff --git a/liblog/logd_reader.cpp b/liblog/logd_reader.cpp
index 611caed..6bff078 100644
--- a/liblog/logd_reader.cpp
+++ b/liblog/logd_reader.cpp
@@ -96,8 +96,6 @@
len = buf_size;
cp = buf;
while ((ret = TEMP_FAILURE_RETRY(read(sock, cp, len))) > 0) {
- struct pollfd p;
-
if (((size_t)ret == len) || (buf_size < PAGE_SIZE)) {
break;
}
@@ -105,11 +103,8 @@
len -= ret;
cp += ret;
- memset(&p, 0, sizeof(p));
- p.fd = sock;
- p.events = POLLIN;
-
- /* Give other side 20ms to refill pipe */
+ // Give other side 20ms to refill pipe.
+ struct pollfd p = {.fd = sock, .events = POLLIN};
ret = TEMP_FAILURE_RETRY(poll(&p, 1, 20));
if (ret <= 0) {
diff --git a/liblog/logprint.cpp b/liblog/logprint.cpp
index 639b8d9..1667bf0 100644
--- a/liblog/logprint.cpp
+++ b/liblog/logprint.cpp
@@ -1,19 +1,18 @@
/*
-**
-** Copyright 2006-2014, 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.
-*/
+ * Copyright 2006, 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 <log/logprint.h>
@@ -1314,6 +1313,7 @@
suspended_pending = false;
}
}
+ free(line);
pclose(p);
}
/* last entry is our current time conversion */
@@ -1691,42 +1691,22 @@
return ret;
}
-/**
- * Either print or do not print log line, based on filter
- *
- * Returns count bytes written
- */
-
-int android_log_printLogLine(AndroidLogFormat* p_format, int fd, const AndroidLogEntry* entry) {
- int ret;
- char defaultBuffer[512];
- char* outBuffer = NULL;
- size_t totalLen;
-
- outBuffer =
- android_log_formatLogLine(p_format, defaultBuffer, sizeof(defaultBuffer), entry, &totalLen);
-
- if (!outBuffer) return -1;
-
- do {
- ret = write(fd, outBuffer, totalLen);
- } while (ret < 0 && errno == EINTR);
-
- if (ret < 0) {
- fprintf(stderr, "+++ LOG: write failed (errno=%d)\n", errno);
- ret = 0;
- goto done;
+size_t android_log_printLogLine(AndroidLogFormat* p_format, FILE* fp,
+ const AndroidLogEntry* entry) {
+ char buf[4096] __attribute__((__uninitialized__));
+ size_t line_length;
+ char* line = android_log_formatLogLine(p_format, buf, sizeof(buf), entry, &line_length);
+ if (!line) {
+ fprintf(stderr, "android_log_formatLogLine failed\n");
+ exit(1);
}
- if (((size_t)ret) < totalLen) {
- fprintf(stderr, "+++ LOG: write partial (%d of %d)\n", ret, (int)totalLen);
- goto done;
+ size_t bytesWritten = fwrite(line, 1, line_length, fp);
+ if (bytesWritten != line_length) {
+ perror("fwrite failed");
+ exit(1);
}
-done:
- if (outBuffer != defaultBuffer) {
- free(outBuffer);
- }
-
- return ret;
+ if (line != buf) free(line);
+ return bytesWritten;
}
diff --git a/liblog/pmsg_reader.cpp b/liblog/pmsg_reader.cpp
index 5640900..cf89366 100644
--- a/liblog/pmsg_reader.cpp
+++ b/liblog/pmsg_reader.cpp
@@ -162,7 +162,6 @@
ssize_t __android_log_pmsg_file_read(log_id_t logId, char prio, const char* prefix,
__android_log_pmsg_file_read_fn fn, void* arg) {
ssize_t ret;
- struct logger_list logger_list;
struct content {
struct listnode node;
struct logger_entry entry;
@@ -183,10 +182,9 @@
}
/* Add just enough clues in logger_list and transp to make API function */
- memset(&logger_list, 0, sizeof(logger_list));
-
- logger_list.mode = ANDROID_LOG_PSTORE | ANDROID_LOG_NONBLOCK;
- logger_list.log_mask = (unsigned)-1;
+ struct logger_list logger_list = {
+ .mode = static_cast<int>(ANDROID_LOG_PSTORE | ANDROID_LOG_NONBLOCK),
+ .log_mask = (unsigned)-1};
if (logId != LOG_ID_ANY) {
logger_list.log_mask = (1 << logId);
}
diff --git a/liblog/tests/liblog_benchmark.cpp b/liblog/tests/liblog_benchmark.cpp
index 5f1750b..5a8364c 100644
--- a/liblog/tests/liblog_benchmark.cpp
+++ b/liblog/tests/liblog_benchmark.cpp
@@ -267,8 +267,7 @@
android_log_header_t header;
android_log_event_int_t payload;
};
- alignas(8) char buf[sizeof(struct packet) + 8];
- memset(buf, 0, sizeof(buf));
+ alignas(8) char buf[sizeof(struct packet) + 8] = {};
struct packet* buffer = (struct packet*)(((uintptr_t)buf + 7) & ~7);
if (((uintptr_t)&buffer->pmsg_header) & 7) {
fprintf(stderr, "&buffer=0x%p iterations=%" PRIu64 "\n", &buffer->pmsg_header,
@@ -342,8 +341,7 @@
android_log_header_t header;
android_log_event_int_t payload;
};
- alignas(8) char buf[sizeof(struct packet) + 8];
- memset(buf, 0, sizeof(buf));
+ alignas(8) char buf[sizeof(struct packet) + 8] = {};
struct packet* buffer = (struct packet*)((((uintptr_t)buf + 7) & ~7) + 1);
if ((((uintptr_t)&buffer->pmsg_header) & 7) != 1) {
fprintf(stderr, "&buffer=0x%p iterations=%" PRIu64 "\n", &buffer->pmsg_header,
@@ -417,8 +415,7 @@
android_log_header_t header;
android_log_event_int_t payload;
};
- alignas(8) char buf[sizeof(struct packet) + 8 + LOGGER_ENTRY_MAX_PAYLOAD];
- memset(buf, 0, sizeof(buf));
+ alignas(8) char buf[sizeof(struct packet) + 8 + LOGGER_ENTRY_MAX_PAYLOAD] = {};
struct packet* buffer = (struct packet*)(((uintptr_t)buf + 7) & ~7);
if (((uintptr_t)&buffer->pmsg_header) & 7) {
fprintf(stderr, "&buffer=0x%p iterations=%" PRIu64 "\n", &buffer->pmsg_header,
@@ -490,8 +487,7 @@
android_log_header_t header;
android_log_event_int_t payload;
};
- alignas(8) char buf[sizeof(struct packet) + 8 + LOGGER_ENTRY_MAX_PAYLOAD];
- memset(buf, 0, sizeof(buf));
+ alignas(8) char buf[sizeof(struct packet) + 8 + LOGGER_ENTRY_MAX_PAYLOAD] = {};
struct packet* buffer = (struct packet*)((((uintptr_t)buf + 7) & ~7) + 1);
if ((((uintptr_t)&buffer->pmsg_header) & 7) != 1) {
fprintf(stderr, "&buffer=0x%p iterations=%" PRIu64 "\n", &buffer->pmsg_header,
@@ -931,8 +927,7 @@
}
while (state.KeepRunning()) {
- char buffer[256];
- memset(buffer, 0, sizeof(buffer));
+ char buffer[256] = {};
log_time now(CLOCK_MONOTONIC);
char name[64];
snprintf(name, sizeof(name), "a%" PRIu64, now.nsec());
diff --git a/liblog/tests/liblog_test.cpp b/liblog/tests/liblog_test.cpp
index c49d87b..d09f9e1 100644
--- a/liblog/tests/liblog_test.cpp
+++ b/liblog/tests/liblog_test.cpp
@@ -386,11 +386,11 @@
if (pid > 999999) ++line_overhead;
fflush(stderr);
if (processBinaryLogBuffer) {
- EXPECT_GT((int)((line_overhead * num_lines) + size),
- android_log_printLogLine(logformat, fileno(stderr), &entry));
+ EXPECT_GT((line_overhead * num_lines) + size,
+ android_log_printLogLine(logformat, stderr, &entry));
} else {
- EXPECT_EQ((int)((line_overhead * num_lines) + size),
- android_log_printLogLine(logformat, fileno(stderr), &entry));
+ EXPECT_EQ((line_overhead * num_lines) + size,
+ android_log_printLogLine(logformat, stderr, &entry));
}
}
android_log_format_free(logformat);
@@ -468,8 +468,8 @@
if (pid > 99999) ++line_overhead;
if (pid > 999999) ++line_overhead;
fflush(stderr);
- EXPECT_EQ((int)(((line_overhead + sizeof(tag)) * num_lines) + size),
- android_log_printLogLine(logformat, fileno(stderr), &entry));
+ EXPECT_EQ(((line_overhead + sizeof(tag)) * num_lines) + size,
+ android_log_printLogLine(logformat, stderr, &entry));
}
android_log_format_free(logformat);
};
@@ -688,9 +688,7 @@
return -1;
}
- struct sched_param param;
-
- memset(¶m, 0, sizeof(param));
+ struct sched_param param = {};
pthread_attr_setschedparam(&attr, ¶m);
pthread_attr_setschedpolicy(&attr, SCHED_BATCH);
@@ -1006,8 +1004,7 @@
EXPECT_EQ(0, processLogBuffer);
if (processLogBuffer == 0) {
fflush(stderr);
- int printLogLine =
- android_log_printLogLine(logformat, fileno(stderr), &entry);
+ int printLogLine = android_log_printLogLine(logformat, stderr, &entry);
// Legacy tag truncation
EXPECT_LE(128, printLogLine);
// Measured maximum if we try to print part of the tag as message
@@ -1241,7 +1238,7 @@
static const size_t base_offset = 8; /* skip "persist." */
// sizeof("string") = strlen("string") + 1
char key[sizeof(log_namespace) + sizeof(tag) - 1];
- char hold[4][PROP_VALUE_MAX];
+ char hold[4][PROP_VALUE_MAX] = {};
static const struct {
int level;
char type;
@@ -1253,7 +1250,6 @@
};
// Set up initial test condition
- memset(hold, 0, sizeof(hold));
snprintf(key, sizeof(key), "%s%s", log_namespace, tag);
property_get(key, hold[0], "");
property_set(key, "");
@@ -2064,7 +2060,7 @@
static int android_log_buffer_to_string(const char* msg, size_t len,
char* strOut, size_t strOutLen) {
android_log_context context = create_android_log_parser(msg, len);
- android_log_list_element elem;
+ android_log_list_element elem = {};
bool overflow = false;
/* Reserve 1 byte for null terminator. */
size_t origStrOutLen = strOutLen--;
@@ -2073,8 +2069,6 @@
return -EBADF;
}
- memset(&elem, 0, sizeof(elem));
-
size_t outCount;
do {
@@ -2493,8 +2487,7 @@
if (pid > 99999) ++line_overhead;
if (pid > 999999) ++line_overhead;
print_barrier();
- int printLogLine =
- android_log_printLogLine(logformat, fileno(stderr), &entry);
+ int printLogLine = android_log_printLogLine(logformat, stderr, &entry);
print_barrier();
EXPECT_EQ(line_overhead + (int)strlen(expected_string), printLogLine);
}
diff --git a/liblog/tests/log_wrap_test.cpp b/liblog/tests/log_wrap_test.cpp
index 755898a..4288ead 100644
--- a/liblog/tests/log_wrap_test.cpp
+++ b/liblog/tests/log_wrap_test.cpp
@@ -33,8 +33,7 @@
// Read the last line in the log to get a starting timestamp. We're assuming
// the log is not empty.
const int mode = ANDROID_LOG_NONBLOCK;
- struct logger_list* logger_list =
- android_logger_list_open(LOG_ID_MAIN, mode, 1000, 0);
+ struct logger_list* logger_list = android_logger_list_open(LOG_ID_SYSTEM, mode, 1000, 0);
ASSERT_NE(logger_list, nullptr);
@@ -49,8 +48,7 @@
logger_list =
android_logger_list_alloc_time(mode | ANDROID_LOG_WRAP, start, 0);
ASSERT_NE(logger_list, nullptr);
-
- struct logger* logger = android_logger_open(logger_list, LOG_ID_MAIN);
+ struct logger* logger = android_logger_open(logger_list, LOG_ID_SYSTEM);
EXPECT_NE(logger, nullptr);
if (logger) {
android_logger_list_read(logger_list, &log_msg);
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index 25fda5d..9304399 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2006-2017 The Android Open Source Project
+ * Copyright (C) 2006 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.
@@ -20,6 +20,7 @@
#include <error.h>
#include <fcntl.h>
#include <getopt.h>
+#include <linux/f2fs.h>
#include <math.h>
#include <sched.h>
#include <stdarg.h>
@@ -64,6 +65,7 @@
using android::base::ParseUint;
using android::base::Split;
using android::base::StringPrintf;
+using android::base::WaitForProperty;
using android::base::WriteFully;
class Logcat {
@@ -76,17 +78,24 @@
void PrintDividers(log_id_t log_id, bool print_dividers);
void SetupOutputAndSchedulingPolicy(bool blocking);
int SetLogFormat(const char* format_string);
+ void WriteFully(const void* p, size_t n) {
+ if (fwrite(p, 1, n, output_file_) != n) {
+ error(EXIT_FAILURE, errno, "Write to output file failed");
+ }
+ }
// Used for all options
- android::base::unique_fd output_fd_{dup(STDOUT_FILENO)};
std::unique_ptr<AndroidLogFormat, decltype(&android_log_format_free)> logformat_{
android_log_format_new(), &android_log_format_free};
+ // This isn't a unique_ptr because it's usually stdout;
+ // stdio's atexit handler ensures we flush on exit.
+ FILE* output_file_ = stdout;
// For logging to a file and log rotation
const char* output_file_name_ = nullptr;
size_t log_rotate_size_kb_ = 0; // 0 means "no log rotation"
size_t max_rotated_logs_ = DEFAULT_MAX_ROTATED_LOGS; // 0 means "unbounded"
- size_t out_byte_count_ = 0;
+ uint64_t out_byte_count_ = 0;
// For binary log buffers
int print_binary_ = 0;
@@ -108,41 +117,38 @@
bool debug_ = false;
};
-#ifndef F2FS_IOC_SET_PIN_FILE
-#define F2FS_IOCTL_MAGIC 0xf5
-#define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32)
-#endif
-
-static int openLogFile(const char* pathname, size_t sizeKB) {
- int fd = open(pathname, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP);
- if (fd < 0) {
- return fd;
- }
-
- // no need to check errors
- __u32 set = 1;
+static void pinLogFile(int fd, size_t sizeKB) {
+ // Ignore errors.
+ uint32_t set = 1;
ioctl(fd, F2FS_IOC_SET_PIN_FILE, &set);
fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, (sizeKB << 10));
- return fd;
}
-static void closeLogFile(const char* pathname) {
+static void unpinLogFile(const char* pathname) {
int fd = open(pathname, O_WRONLY | O_CLOEXEC);
- if (fd == -1) {
- return;
+ if (fd != -1) {
+ // Ignore errors.
+ uint32_t set = 0;
+ ioctl(fd, F2FS_IOC_SET_PIN_FILE, &set);
+ close(fd);
}
+}
- // no need to check errors
- __u32 set = 0;
- ioctl(fd, F2FS_IOC_SET_PIN_FILE, &set);
- close(fd);
+static FILE* openLogFile(const char* path, size_t sizeKB) {
+ int fd = open(path, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP);
+ if (fd == -1) {
+ error(EXIT_FAILURE, errno, "couldn't open output file '%s'", path);
+ }
+ pinLogFile(fd, sizeKB);
+ return fdopen(fd, "w");
}
void Logcat::RotateLogs() {
// Can't rotate logs if we're not outputting to a file
if (!output_file_name_) return;
- output_fd_.reset();
+ fclose(output_file_);
+ output_file_ = nullptr;
// Compute the maximum number of digits needed to count up to
// maxRotatedLogs in decimal. eg:
@@ -168,40 +174,34 @@
break;
}
- closeLogFile(file0.c_str());
+ unpinLogFile(file0.c_str());
- int err = rename(file0.c_str(), file1.c_str());
-
- if (err < 0 && errno != ENOENT) {
- perror("while rotating log files");
+ if (rename(file0.c_str(), file1.c_str()) == -1 && errno != ENOENT) {
+ error(0, errno, "rename('%s', '%s') failed while rotating log files", file0.c_str(),
+ file1.c_str());
}
}
- output_fd_.reset(openLogFile(output_file_name_, log_rotate_size_kb_));
-
- if (!output_fd_.ok()) {
- error(EXIT_FAILURE, errno, "Couldn't open output file");
- }
-
+ output_file_ = openLogFile(output_file_name_, log_rotate_size_kb_);
out_byte_count_ = 0;
}
void Logcat::ProcessBuffer(struct log_msg* buf) {
- int bytesWritten = 0;
- int err;
AndroidLogEntry entry;
- char binaryMsgBuf[1024];
+ char binaryMsgBuf[1024] __attribute__((__uninitialized__));
bool is_binary =
buf->id() == LOG_ID_EVENTS || buf->id() == LOG_ID_STATS || buf->id() == LOG_ID_SECURITY;
-
+ int err;
if (is_binary) {
if (!event_tag_map_ && !has_opened_event_tag_map_) {
event_tag_map_.reset(android_openEventTagMap(nullptr));
has_opened_event_tag_map_ = true;
}
+ // This causes entry to point to binaryMsgBuf!
err = android_log_processBinaryLogBuffer(&buf->entry, &entry, event_tag_map_.get(),
binaryMsgBuf, sizeof(binaryMsgBuf));
+
// printf(">>> pri=%d len=%d msg='%s'\n",
// entry.priority, entry.messageLen, entry.message);
} else {
@@ -217,17 +217,10 @@
print_count_ += match;
if (match || print_it_anyway_) {
PrintDividers(buf->id(), print_dividers_);
-
- bytesWritten = android_log_printLogLine(logformat_.get(), output_fd_.get(), &entry);
-
- if (bytesWritten < 0) {
- error(EXIT_FAILURE, 0, "Output error.");
- }
+ out_byte_count_ += android_log_printLogLine(logformat_.get(), output_file_, &entry);
}
}
- out_byte_count_ += bytesWritten;
-
if (log_rotate_size_kb_ > 0 && (out_byte_count_ / 1024) >= log_rotate_size_kb_) {
RotateLogs();
}
@@ -238,7 +231,7 @@
return;
}
if (!printed_start_[log_id] || print_dividers) {
- if (dprintf(output_fd_.get(), "--------- %s %s\n",
+ if (fprintf(output_file_, "--------- %s %s\n",
printed_start_[log_id] ? "switch to" : "beginning of",
android_log_id_to_name(log_id)) < 0) {
error(EXIT_FAILURE, errno, "Output error");
@@ -268,22 +261,13 @@
}
}
- output_fd_.reset(openLogFile(output_file_name_, log_rotate_size_kb_));
+ output_file_ = openLogFile(output_file_name_, log_rotate_size_kb_);
- if (!output_fd_.ok()) {
- error(EXIT_FAILURE, errno, "Couldn't open output file");
+ struct stat sb;
+ if (fstat(fileno(output_file_), &sb) == -1) {
+ error(EXIT_FAILURE, errno, "Couldn't stat output file");
}
-
- struct stat statbuf;
- if (fstat(output_fd_.get(), &statbuf) == -1) {
- error(EXIT_FAILURE, errno, "Couldn't get output file stat");
- }
-
- if ((size_t)statbuf.st_size > SIZE_MAX || statbuf.st_size < 0) {
- error(EXIT_FAILURE, 0, "Invalid output file stat.");
- }
-
- out_byte_count_ = statbuf.st_size;
+ out_byte_count_ = sb.st_size;
}
// clang-format off
@@ -898,13 +882,13 @@
}
if (mode & ANDROID_LOG_PSTORE) {
- if (output_file_name_) {
- error(EXIT_FAILURE, 0, "-c is ambiguous with both -f and -L specified.");
- }
if (setLogSize || getLogSize || printStatistics || getPruneList || setPruneList) {
error(EXIT_FAILURE, 0, "-L is incompatible with -g/-G, -S, and -p/-P.");
}
if (clearLog) {
+ if (output_file_name_) {
+ error(EXIT_FAILURE, 0, "-c is ambiguous with both -f and -L specified.");
+ }
unlink("/sys/fs/pstore/pmsg-ramoops-0");
return EXIT_SUCCESS;
}
@@ -995,9 +979,7 @@
buffer_name, size_format.first, size_format.second, consumed_format.first,
consumed_format.second, readable_format.first, readable_format.second,
(int)LOGGER_ENTRY_MAX_LEN, (int)LOGGER_ENTRY_MAX_PAYLOAD);
- if (!WriteFully(output_fd_, str.data(), str.length())) {
- error(EXIT_FAILURE, errno, "Failed to write to output fd");
- }
+ WriteFully(str.data(), str.length());
}
}
}
@@ -1066,16 +1048,18 @@
while (isdigit(*cp)) ++cp;
if (*cp == '\n') ++cp;
- size_t len = strlen(cp);
- if (!WriteFully(output_fd_, cp, len)) {
- error(EXIT_FAILURE, errno, "Failed to write to output fd");
- }
+ WriteFully(cp, strlen(cp));
return EXIT_SUCCESS;
}
if (getLogSize || setLogSize || clearLog) return EXIT_SUCCESS;
- SetupOutputAndSchedulingPolicy(!(mode & ANDROID_LOG_NONBLOCK));
+ bool blocking = !(mode & ANDROID_LOG_NONBLOCK);
+ SetupOutputAndSchedulingPolicy(blocking);
+
+ if (!WaitForProperty("logd.ready", "true", std::chrono::seconds(1))) {
+ error(EXIT_FAILURE, 0, "Failed to wait for logd.ready to become true. logd not running?");
+ }
while (!max_count_ || print_count_ < max_count_) {
struct log_msg log_msg;
@@ -1111,11 +1095,10 @@
}
if (print_binary_) {
- if (!WriteFully(output_fd_, &log_msg, log_msg.len())) {
- error(EXIT_FAILURE, errno, "Failed to write to output fd");
- }
+ WriteFully(&log_msg, log_msg.len());
} else {
ProcessBuffer(&log_msg);
+ if (blocking && output_file_ == stdout) fflush(stdout);
}
}
return EXIT_SUCCESS;
diff --git a/logcat/logcatd.rc b/logcat/logcatd.rc
index 64d5500..eab4d78 100644
--- a/logcat/logcatd.rc
+++ b/logcat/logcatd.rc
@@ -58,5 +58,5 @@
# logd for write to /data/misc/logd, log group for read from log daemon
user logd
group log
- writepid /dev/cpuset/system-background/tasks
+ task_profiles ServiceCapacityLow
oom_score_adjust -600
diff --git a/logcat/tests/logcat_test.cpp b/logcat/tests/logcat_test.cpp
index b835bd5..1e0fc9d 100644
--- a/logcat/tests/logcat_test.cpp
+++ b/logcat/tests/logcat_test.cpp
@@ -496,8 +496,7 @@
ASSERT_LT(0, __android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts)));
- FILE* fp[256]; // does this count as a multitude!
- memset(fp, 0, sizeof(fp));
+ FILE* fp[256] = {}; // does this count as a multitude!
size_t num = 0;
do {
EXPECT_TRUE(NULL != (fp[num] = popen(logcat_executable " -v brief -b events -t 100", "r")));
diff --git a/logd/Android.bp b/logd/Android.bp
index 6f23808..2059a7a 100644
--- a/logd/Android.bp
+++ b/logd/Android.bp
@@ -93,6 +93,7 @@
"LogReader.cpp",
"LogAudit.cpp",
"LogKlog.cpp",
+ "TrustyLog.cpp",
"libaudit.cpp",
"PkgIds.cpp",
],
diff --git a/logd/LogAudit.cpp b/logd/LogAudit.cpp
index 13284cd..17c2237 100644
--- a/logd/LogAudit.cpp
+++ b/logd/LogAudit.cpp
@@ -28,11 +28,11 @@
#include <sys/uio.h>
#include <syslog.h>
-#include <fstream>
-#include <sstream>
-
+#include <android-base/file.h>
+#include <android-base/logging.h>
#include <android-base/macros.h>
#include <android-base/properties.h>
+#include <android-base/strings.h>
#include <private/android_filesystem_config.h>
#include <private/android_logger.h>
@@ -111,21 +111,48 @@
(str[str_len - 9] == '/' || str[str_len - 39] == '/');
}
-std::map<std::string, std::string> LogAudit::populateDenialMap() {
- std::ifstream bug_file("/vendor/etc/selinux/selinux_denial_metadata");
- std::string line;
- // allocate a map for the static map pointer in auditParse to keep track of,
- // this function only runs once
- std::map<std::string, std::string> denial_to_bug;
- if (bug_file.good()) {
- std::string scontext;
- std::string tcontext;
- std::string tclass;
- std::string bug_num;
- while (std::getline(bug_file, line)) {
- std::stringstream split_line(line);
- split_line >> scontext >> tcontext >> tclass >> bug_num;
- denial_to_bug.emplace(scontext + tcontext + tclass, bug_num);
+static auto populateDenialMap() {
+ std::map<std::tuple<std::string, std::string, std::string>, std::string> denial_to_bug;
+ // Order matters. Only the first occurrence of a
+ // (scontext, tcontext, tclass) combination is recorded.
+ for (const auto& bug_map_file :
+ {"/system_ext/etc/selinux/bug_map"s, "/vendor/etc/selinux/selinux_denial_metadata"s,
+ "/system/etc/selinux/bug_map"s}) {
+ std::string file_contents;
+ if (!android::base::ReadFileToString(bug_map_file, &file_contents)) {
+ continue;
+ }
+ int errors = 0;
+ for (const auto& line : android::base::Split(file_contents, "\n")) {
+ const auto fields = android::base::Tokenize(line, " ");
+ if (fields.empty() || android::base::StartsWith(fields.front(), '#')) {
+ continue;
+ }
+ if (fields.size() == 4) {
+ const std::string& scontext = fields[0];
+ const std::string& tcontext = fields[1];
+ const std::string& tclass = fields[2];
+ const std::string& bug_num = fields[3];
+ const auto [it, success] =
+ denial_to_bug.try_emplace({scontext, tcontext, tclass}, bug_num);
+ if (!success) {
+ const auto& [key, value] = *it;
+ LOG(WARNING) << "Ignored bug_map definition in " << bug_map_file << ": '"
+ << line
+ << "', (scontext, tcontext, tclass) denial combination is already "
+ "tagged with bug metadata '"
+ << value << "'";
+ }
+ } else {
+ LOG(ERROR) << "Ignored ill-formed bug_map definition in " << bug_map_file << ": '"
+ << line << "'";
+ ++errors;
+ }
+ }
+ if (errors) {
+ LOG(ERROR) << "Loaded bug_map file with " << errors << " errors: " << bug_map_file;
+ } else {
+ LOG(INFO) << "Loaded bug_map file: " << bug_map_file;
}
}
return denial_to_bug;
@@ -143,8 +170,9 @@
}
std::string LogAudit::auditParse(const std::string& string, uid_t uid) {
- static std::map<std::string, std::string> denial_to_bug =
- populateDenialMap();
+ // Allocate a static map object to memoize the loaded bug_map files.
+ static auto denial_to_bug = populateDenialMap();
+
std::string result;
std::string scontext = denialParse(string, ':', "scontext=u:object_r:");
std::string tcontext = denialParse(string, ':', "tcontext=u:object_r:");
@@ -155,7 +183,7 @@
if (tcontext.empty()) {
tcontext = denialParse(string, ':', "tcontext=u:r:");
}
- auto search = denial_to_bug.find(scontext + tcontext + tclass);
+ auto search = denial_to_bug.find({scontext, tcontext, tclass});
if (search != denial_to_bug.end()) {
result = " bug=" + search->second;
}
diff --git a/logd/LogAudit.h b/logd/LogAudit.h
index e7dd6b4..cc8e508 100644
--- a/logd/LogAudit.h
+++ b/logd/LogAudit.h
@@ -39,7 +39,6 @@
private:
static int getLogSocket();
- std::map<std::string, std::string> populateDenialMap();
std::string denialParse(const std::string& denial, char terminator,
const std::string& search_term);
std::string auditParse(const std::string& string, uid_t uid);
diff --git a/logd/LogKlog.cpp b/logd/LogKlog.cpp
index ab4f815..dc788e1 100644
--- a/logd/LogKlog.cpp
+++ b/logd/LogKlog.cpp
@@ -275,9 +275,7 @@
// Bionic and liblog strptime does not support %z or %Z to pick up
// timezone so we are calculating our own correction.
time_t now = real.tv_sec;
- struct tm tm;
- memset(&tm, 0, sizeof(tm));
- tm.tm_isdst = -1;
+ struct tm tm = {.tm_isdst = -1};
localtime_r(&now, &tm);
if ((tm.tm_gmtoff < 0) && ((-tm.tm_gmtoff) > (long)real.tv_sec)) {
real = log_time(log_time::EPOCH);
diff --git a/logd/LogListener.cpp b/logd/LogListener.cpp
index a6ab50b..ae74796 100644
--- a/logd/LogListener.cpp
+++ b/logd/LogListener.cpp
@@ -62,15 +62,14 @@
nullptr, 0, &iov, 1, control, sizeof(control), 0,
};
- // To clear the entire buffer is secure/safe, but this contributes to 1.68%
- // overhead under logging load. We are safe because we check counts, but
- // still need to clear null terminator
- // memset(buffer, 0, sizeof(buffer));
ssize_t n = recvmsg(socket_, &hdr, 0);
if (n <= (ssize_t)(sizeof(android_log_header_t))) {
return;
}
+ // To clear the entire buffer would be safe, but this contributes to 1.68%
+ // overhead under logging load. We are safe because we check counts, but
+ // still need to clear null terminator
buffer[n] = 0;
struct ucred* cred = nullptr;
diff --git a/logd/LogSize.cpp b/logd/LogSize.cpp
index fe829ba..3ae8746 100644
--- a/logd/LogSize.cpp
+++ b/logd/LogSize.cpp
@@ -27,6 +27,7 @@
return kLogBufferMinSize <= value && value <= kLogBufferMaxSize;
}
+/*
static std::optional<size_t> GetBufferSizeProperty(const std::string& key) {
std::string value = android::base::GetProperty(key, "");
if (value.empty()) {
@@ -44,8 +45,22 @@
return size;
}
+*/
-size_t GetBufferSizeFromProperties(log_id_t log_id) {
+size_t GetBufferSizeFromProperties(log_id_t /*log_id*/) {
+/*
+ * http://b/196856709
+ *
+ * We've been seeing timeouts from logcat in bugreports for years, but the
+ * rate has gone way up lately. The suspicion is that this is because we
+ * have a lot of dogfooders who still have custom (large) log sizes but
+ * the new compressed logging is cramming way more in. The bugreports I've seen
+ * have had 1,000,000+ lines, so taking 10s to collect that much logging seems
+ * plausible. Of course, it's also possible that logcat is timing out because
+ * the log is being *spammed* as it's being read. But temporarily disabling
+ * custom log sizes like this should help us confirm (or deny) whether the
+ * problem really is this simple.
+ *
std::string buffer_name = android_log_id_to_name(log_id);
std::array<std::string, 4> properties = {
"persist.logd.size." + buffer_name,
@@ -59,6 +74,7 @@
return *size;
}
}
+*/
if (android::base::GetBoolProperty("ro.config.low_ram", false)) {
return kLogBufferMinSize;
diff --git a/logd/ReplayMessages.cpp b/logd/ReplayMessages.cpp
index 2382ed0..ae17517 100644
--- a/logd/ReplayMessages.cpp
+++ b/logd/ReplayMessages.cpp
@@ -93,7 +93,7 @@
fprintf(stderr, "Error parsing log message\n");
}
- android_log_printLogLine(GetLogFormat(), STDOUT_FILENO, &entry);
+ android_log_printLogLine(GetLogFormat(), stdout, &entry);
}
static log_time GetFirstTimeStamp(const MappedFile& recorded_messages) {
diff --git a/logd/TrustyLog.cpp b/logd/TrustyLog.cpp
new file mode 100644
index 0000000..213533d
--- /dev/null
+++ b/logd/TrustyLog.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2021 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 "TrustyLog.h"
+#include <private/android_logger.h>
+#include "LogBuffer.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#define TRUSTY_LINE_BUFFER_SIZE 256
+static const char trustyprefix[] = "trusty";
+
+TrustyLog::TrustyLog(LogBuffer* buf, int fdRead) : SocketListener(fdRead, false), logbuf(buf) {}
+
+void TrustyLog::create(LogBuffer* buf) {
+ if (access("/sys/module/trusty_log/parameters/log_size", F_OK)) {
+ /* this device has the old driver which doesn't support poll() */
+ return;
+ }
+
+ int fd = TEMP_FAILURE_RETRY(open("/dev/trusty-log0", O_RDONLY | O_NDELAY | O_CLOEXEC));
+ if (fd >= 0) {
+ TrustyLog* tl = new TrustyLog(buf, fd);
+ if (tl->startListener()) {
+ delete tl;
+ }
+ }
+}
+
+/*
+ * Log a message, breaking it into smaller chunks if needed
+ */
+void TrustyLog::LogMsg(const char* msg, size_t len) {
+ char linebuffer[TRUSTY_LINE_BUFFER_SIZE + sizeof(trustyprefix) + 1];
+
+ while (len) {
+ size_t sublen = len;
+ if (sublen > TRUSTY_LINE_BUFFER_SIZE) {
+ sublen = TRUSTY_LINE_BUFFER_SIZE;
+ }
+
+ *linebuffer = ANDROID_LOG_INFO;
+ strcpy(linebuffer + 1, trustyprefix);
+ strncpy(linebuffer + 1 + sizeof(trustyprefix), msg, sublen);
+ timespec tp;
+ clock_gettime(CLOCK_REALTIME, &tp);
+ log_time now = log_time(tp.tv_sec, tp.tv_nsec);
+ // The Log() API appears to want a length that is 1 greater than what's
+ // actually being logged.
+ logbuf->Log(LOG_ID_KERNEL, now, AID_ROOT, 0 /*pid*/, 0 /*tid*/, linebuffer,
+ sizeof(trustyprefix) + sublen + 2);
+ msg += sublen;
+ len -= sublen;
+ }
+}
+
+bool TrustyLog::onDataAvailable(SocketClient* cli) {
+ char buffer[4096];
+ ssize_t len = 0;
+ for (;;) {
+ ssize_t retval = 0;
+ if (len < (ssize_t)(sizeof(buffer) - 1)) {
+ retval = TEMP_FAILURE_RETRY(
+ read(cli->getSocket(), buffer + len, sizeof(buffer) - 1 - len));
+ }
+ if (retval > 0) {
+ len += retval;
+ }
+ if ((retval <= 0) && (len <= 0)) {
+ // nothing read and nothing to read
+ break;
+ }
+
+ // log the complete lines we have so far
+ char* linestart = buffer;
+ for (;;) {
+ char* lineend = static_cast<char*>(memchr(linestart, '\n', len));
+ if (lineend) {
+ // print one newline-terminated line
+ size_t linelen = lineend - linestart;
+ LogMsg(linestart, linelen);
+ linestart += (linelen + 1); // next line, skipping the newline
+ len -= (linelen + 1);
+ } else if (len >= TRUSTY_LINE_BUFFER_SIZE) {
+ // there was no newline, but there's enough data to print
+ LogMsg(linestart, TRUSTY_LINE_BUFFER_SIZE);
+ linestart += TRUSTY_LINE_BUFFER_SIZE;
+ len -= TRUSTY_LINE_BUFFER_SIZE;
+ } else {
+ if (len) {
+ // there's some unterminated data left at the end of the
+ // buffer. Move it to the front and try to append more in
+ // the outer loop.
+ memmove(buffer, linestart, len);
+ }
+ break;
+ }
+ }
+ }
+ return true;
+}
diff --git a/logd/TrustyLog.h b/logd/TrustyLog.h
new file mode 100644
index 0000000..c9960ec
--- /dev/null
+++ b/logd/TrustyLog.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#pragma once
+
+#include <private/android_logger.h>
+#include <sysutils/SocketListener.h>
+
+#include "LogBuffer.h"
+#include "LogStatistics.h"
+
+/* Use SocketListener because it provides reader thread management and
+ * works well with the Trusty log device due to using poll() and not
+ * relying on blocking reads, which the Trusty log device does not support.
+ */
+class TrustyLog : public SocketListener {
+ LogBuffer* logbuf;
+ void LogMsg(const char* msg, size_t len);
+
+ public:
+ static void create(LogBuffer* buf);
+
+ protected:
+ virtual bool onDataAvailable(SocketClient* cli);
+ TrustyLog(LogBuffer* buf, int fdRead);
+};
diff --git a/logd/logd.rc b/logd/logd.rc
index 530f342..69bfb73 100644
--- a/logd/logd.rc
+++ b/logd/logd.rc
@@ -8,14 +8,15 @@
group logd system package_info readproc
capabilities SYSLOG AUDIT_CONTROL
priority 10
- writepid /dev/cpuset/system-background/tasks
+ task_profiles ServiceCapacityLow
+ onrestart setprop logd.ready false
service logd-reinit /system/bin/logd --reinit
oneshot
disabled
user logd
group logd
- writepid /dev/cpuset/system-background/tasks
+ task_profiles ServiceCapacityLow
# Limit SELinux denial generation to 5/second
service logd-auditctl /system/bin/auditctl -r 5
diff --git a/logd/logd_test.cpp b/logd/logd_test.cpp
index e2223b6..1de5fcf 100644
--- a/logd/logd_test.cpp
+++ b/logd/logd_test.cpp
@@ -234,10 +234,9 @@
socket_local_client("logdr", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET));
ASSERT_LT(0, fd) << "Failed to open logdr: " << strerror(errno);
- struct sigaction ignore, old_sigaction;
- memset(&ignore, 0, sizeof(ignore));
- ignore.sa_handler = caught_signal;
+ struct sigaction ignore = {.sa_handler = caught_signal};
sigemptyset(&ignore.sa_mask);
+ struct sigaction old_sigaction;
sigaction(SIGALRM, &ignore, &old_sigaction);
unsigned int old_alarm = alarm(3);
@@ -340,10 +339,9 @@
".%09" PRIu32,
start.tv_sec, start.tv_nsec);
- struct sigaction ignore, old_sigaction;
- memset(&ignore, 0, sizeof(ignore));
- ignore.sa_handler = caught_signal;
+ struct sigaction ignore = {.sa_handler = caught_signal};
sigemptyset(&ignore.sa_mask);
+ struct sigaction old_sigaction;
sigaction(SIGALRM, &ignore, &old_sigaction);
unsigned int old_alarm = alarm(3);
@@ -444,10 +442,9 @@
unique_fd fd(socket_local_client("logdr", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET));
ASSERT_LT(0, fd) << "Failed to open logdr: " << strerror(errno);
- struct sigaction ignore, old_sigaction;
- memset(&ignore, 0, sizeof(ignore));
- ignore.sa_handler = caught_signal;
+ struct sigaction ignore = {.sa_handler = caught_signal};
sigemptyset(&ignore.sa_mask);
+ struct sigaction old_sigaction;
sigaction(SIGALRM, &ignore, &old_sigaction);
unsigned int old_alarm = alarm(alarm_time);
diff --git a/logd/main.cpp b/logd/main.cpp
index d0d2ac7..f8be2dc 100644
--- a/logd/main.cpp
+++ b/logd/main.cpp
@@ -60,6 +60,7 @@
#include "LogUtils.h"
#include "SerializedLogBuffer.h"
#include "SimpleLogBuffer.h"
+#include "TrustyLog.h"
using android::base::GetBoolProperty;
using android::base::GetProperty;
@@ -162,17 +163,13 @@
ssize_t ret = TEMP_FAILURE_RETRY(write(sock, reinitStr, sizeof(reinitStr)));
if (ret < 0) return -errno;
- struct pollfd p;
- memset(&p, 0, sizeof(p));
- p.fd = sock;
- p.events = POLLIN;
+ struct pollfd p = {.fd = sock, .events = POLLIN};
ret = TEMP_FAILURE_RETRY(poll(&p, 1, 1000));
if (ret < 0) return -errno;
if ((ret == 0) || !(p.revents & POLLIN)) return -ETIME;
static const char success[] = "success";
- char buffer[sizeof(success) - 1];
- memset(buffer, 0, sizeof(buffer));
+ char buffer[sizeof(success) - 1] = {};
ret = TEMP_FAILURE_RETRY(read(sock, buffer, sizeof(buffer)));
if (ret < 0) return -errno;
@@ -282,6 +279,9 @@
return EXIT_FAILURE;
}
+ // Notify that others can now interact with logd
+ SetProperty("logd.ready", "true");
+
// LogAudit listens on NETLINK_AUDIT socket for selinux
// initiated log messages. New log entries are added to LogBuffer
// and LogReader is notified to send updates to connected clients.
@@ -307,6 +307,8 @@
delete al;
}
+ TrustyLog::create(log_buffer);
+
TEMP_FAILURE_RETRY(pause());
return EXIT_SUCCESS;
diff --git a/rust/Android.bp b/rust/Android.bp
index 0292da1..61f7d04 100644
--- a/rust/Android.bp
+++ b/rust/Android.bp
@@ -11,6 +11,7 @@
"libenv_logger",
"liblog_rust",
],
+ vendor_available: true,
target: {
android: {
rustlibs: [
@@ -18,6 +19,11 @@
]
},
},
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.uwb",
+ ],
+ min_sdk_version: "29",
}
rust_library {
@@ -58,49 +64,29 @@
// The following tests are each run as separate targets because they all require a clean init state.
rust_test {
- name: "logger_device_test_default_init",
+ name: "logger_test_default_init",
defaults: ["liblogger_test_defaults"],
- srcs: ["tests/default_init.rs"],
-}
-
-rust_test_host {
- name: "logger_host_test_default_init",
- defaults: ["liblogger_test_defaults"],
+ host_supported: true,
srcs: ["tests/default_init.rs"],
}
rust_test {
- name: "logger_device_test_env_log_level",
+ name: "logger_test_env_log_level",
defaults: ["liblogger_test_defaults"],
- srcs: ["tests/env_log_level.rs"],
-}
-
-rust_test_host {
- name: "logger_host_test_env_log_level",
- defaults: ["liblogger_test_defaults"],
+ host_supported: true,
srcs: ["tests/env_log_level.rs"],
}
rust_test {
- name: "logger_device_test_config_log_level",
+ name: "logger_test_config_log_level",
defaults: ["liblogger_test_defaults"],
- srcs: ["tests/config_log_level.rs"],
-}
-
-rust_test_host {
- name: "logger_host_test_config_log_level",
- defaults: ["liblogger_test_defaults"],
+ host_supported: true,
srcs: ["tests/config_log_level.rs"],
}
rust_test {
- name: "logger_device_test_multiple_init",
+ name: "logger_test_multiple_init",
defaults: ["liblogger_test_defaults"],
- srcs: ["tests/multiple_init.rs"],
-}
-
-rust_test_host {
- name: "logger_host_test_multiple_init",
- defaults: ["liblogger_test_defaults"],
+ host_supported: true,
srcs: ["tests/multiple_init.rs"],
}
diff --git a/rust/TEST_MAPPING b/rust/TEST_MAPPING
index 6f4be05..35a2a88 100644
--- a/rust/TEST_MAPPING
+++ b/rust/TEST_MAPPING
@@ -6,16 +6,16 @@
"name": "logger_device_unit_tests"
},
{
- "name": "logger_device_test_default_init"
+ "name": "logger_test_default_init"
},
{
- "name": "logger_device_test_env_log_level"
+ "name": "logger_test_env_log_level"
},
{
- "name": "logger_device_test_config_log_level"
+ "name": "logger_test_config_log_level"
},
{
- "name": "logger_device_test_multiple_init"
+ "name": "logger_test_multiple_init"
}
]
}
\ No newline at end of file