lmkd: Change procfs read routine to handle files larger than 1 page in size

Files like /proc/zoneinfo or /proc/<pid>/status can be larger than 4KB
page size. Change reread_file routine to resize read buffer whenever
it is not big enough to read the entire file. Start with 1-page buffer
and double its size until it's big enough to read the entire file.
Read /proc/zoneinfo during initialization to initialize the buffer
to a big enough size and avoid re-allocations when under memory pressure.

Bug: 137010962
Test: lmkd_unit_test
Change-Id: If9a5b0d27c2f4de9063f0fd0f36f908ece87dcce
Signed-off-by: Suren Baghdasaryan <surenb@google.com>
diff --git a/lmkd/lmkd.c b/lmkd/lmkd.c
index bf0ae4d..f30608e 100644
--- a/lmkd/lmkd.c
+++ b/lmkd/lmkd.c
@@ -424,28 +424,50 @@
  * memory pressure to minimize file opening which by itself requires kernel
  * memory allocation and might result in a stall on memory stressed system.
  */
-static int reread_file(struct reread_data *data, char *buf, size_t buf_size) {
+static char *reread_file(struct reread_data *data) {
+    /* start with page-size buffer and increase if needed */
+    static ssize_t buf_size = PAGE_SIZE;
+    static char *new_buf, *buf = NULL;
     ssize_t size;
 
     if (data->fd == -1) {
-        data->fd = open(data->filename, O_RDONLY | O_CLOEXEC);
-        if (data->fd == -1) {
+        /* First-time buffer initialization */
+        if (!buf && (buf = malloc(buf_size)) == NULL) {
+            return NULL;
+        }
+
+        data->fd = TEMP_FAILURE_RETRY(open(data->filename, O_RDONLY | O_CLOEXEC));
+        if (data->fd < 0) {
             ALOGE("%s open: %s", data->filename, strerror(errno));
-            return -1;
+            return NULL;
         }
     }
 
-    size = read_all(data->fd, buf, buf_size - 1);
-    if (size < 0) {
-        ALOGE("%s read: %s", data->filename, strerror(errno));
-        close(data->fd);
-        data->fd = -1;
-        return -1;
+    while (true) {
+        size = read_all(data->fd, buf, buf_size - 1);
+        if (size < 0) {
+            ALOGE("%s read: %s", data->filename, strerror(errno));
+            close(data->fd);
+            data->fd = -1;
+            return NULL;
+        }
+        if (size < buf_size - 1) {
+            break;
+        }
+        /*
+         * Since we are reading /proc files we can't use fstat to find out
+         * the real size of the file. Double the buffer size and keep retrying.
+         */
+        if ((new_buf = realloc(buf, buf_size * 2)) == NULL) {
+            errno = ENOMEM;
+            return NULL;
+        }
+        buf = new_buf;
+        buf_size *= 2;
     }
-    ALOG_ASSERT((size_t)size < buf_size - 1, "%s too large", data->filename);
     buf[size] = 0;
 
-    return 0;
+    return buf;
 }
 
 static struct proc *pid_lookup(int pid) {
@@ -1200,13 +1222,13 @@
         .filename = ZONEINFO_PATH,
         .fd = -1,
     };
-    char buf[PAGE_SIZE];
+    char *buf;
     char *save_ptr;
     char *line;
 
     memset(zi, 0, sizeof(union zoneinfo));
 
-    if (reread_file(&file_data, buf, sizeof(buf)) < 0) {
+    if ((buf = reread_file(&file_data)) == NULL) {
         return -1;
     }
 
@@ -1254,13 +1276,13 @@
         .filename = MEMINFO_PATH,
         .fd = -1,
     };
-    char buf[PAGE_SIZE];
+    char *buf;
     char *save_ptr;
     char *line;
 
     memset(mi, 0, sizeof(union meminfo));
 
-    if (reread_file(&file_data, buf, sizeof(buf)) < 0) {
+    if ((buf = reread_file(&file_data)) == NULL) {
         return -1;
     }
 
@@ -1542,9 +1564,9 @@
 static int64_t get_memory_usage(struct reread_data *file_data) {
     int ret;
     int64_t mem_usage;
-    char buf[32];
+    char *buf;
 
-    if (reread_file(file_data, buf, sizeof(buf)) < 0) {
+    if ((buf = reread_file(file_data)) == NULL) {
         return -1;
     }
 
@@ -2014,6 +2036,10 @@
 #endif
 
 static int init(void) {
+    struct reread_data file_data = {
+        .filename = ZONEINFO_PATH,
+        .fd = -1,
+    };
     struct epoll_event epev;
     int i;
     int ret;
@@ -2091,6 +2117,14 @@
 
     memset(killcnt_idx, KILLCNT_INVALID_IDX, sizeof(killcnt_idx));
 
+    /*
+     * Read zoneinfo as the biggest file we read to create and size the initial
+     * read buffer and avoid memory re-allocations during memory pressure
+     */
+    if (reread_file(&file_data) == NULL) {
+        ALOGE("Failed to read %s: %s", file_data.filename, strerror(errno));
+    }
+
     return 0;
 }