1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 //
18 // Strictly to deal with reboot into system after OTA after /data
19 // mounts to pull the last pmsg file data and place it
20 // into /data/misc/recovery/ directory, rotating it in.
21 //
22 // Usage: recovery-persist [--force-persist]
23 //
24 //    On systems without /cache mount, all file content representing in the
25 //    recovery/ directory stored in /sys/fs/pstore/pmsg-ramoops-0 in logger
26 //    format that reside in the LOG_ID_SYSTEM buffer at ANDROID_LOG_INFO
27 //    priority or higher is transfered to the /data/misc/recovery/ directory.
28 //    The content is matched and rotated in as need be.
29 //
30 //    --force-persist  ignore /cache mount, always rotate in the contents.
31 //
32 
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 
38 #include <array>
39 #include <string>
40 
41 #include <android-base/file.h>
42 #include <android-base/logging.h>
43 #include <android-base/unique_fd.h>
44 #include <private/android_logger.h> /* private pmsg functions */
45 
46 #include "recovery_utils/logging.h"
47 #include "recovery_utils/parse_install_logs.h"
48 
49 constexpr const char* LAST_LOG_FILE = "/data/misc/recovery/last_log";
50 constexpr const char* LAST_PMSG_FILE = "/sys/fs/pstore/pmsg-ramoops-0";
51 constexpr const char* LAST_KMSG_FILE = "/data/misc/recovery/last_kmsg";
52 constexpr const char* LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops-0";
53 constexpr const char* ALT_LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops";
54 
55 // close a file, log an error if the error indicator is set
check_and_fclose(FILE * fp,const char * name)56 static void check_and_fclose(FILE *fp, const char *name) {
57     fflush(fp);
58     if (ferror(fp)) {
59         PLOG(ERROR) << "Error in " << name;
60     }
61     fclose(fp);
62 }
63 
copy_file(const char * source,const char * destination)64 static void copy_file(const char* source, const char* destination) {
65   FILE* dest_fp = fopen(destination, "we");
66   if (dest_fp == nullptr) {
67     PLOG(ERROR) << "Can't open " << destination;
68   } else {
69     FILE* source_fp = fopen(source, "re");
70     if (source_fp != nullptr) {
71       char buf[4096];
72       size_t bytes;
73       while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) {
74         fwrite(buf, 1, bytes, dest_fp);
75       }
76       check_and_fclose(source_fp, source);
77     }
78     check_and_fclose(dest_fp, destination);
79   }
80 }
81 
file_exists(const char * filename)82 static bool file_exists(const char* filename) {
83   return access(filename, R_OK) == 0;
84 }
85 
86 static bool rotated = false;
87 
logsave(log_id_t,char,const char * filename,const char * buf,size_t len,void *)88 ssize_t logsave(
89         log_id_t /* logId */,
90         char /* prio */,
91         const char *filename,
92         const char *buf, size_t len,
93         void * /* arg */) {
94 
95     std::string destination("/data/misc/");
96     destination += filename;
97 
98     std::string buffer(buf, len);
99 
100     {
101         std::string content;
102         android::base::ReadFileToString(destination, &content);
103 
104         if (buffer.compare(content) == 0) {
105             return len;
106         }
107     }
108 
109     // ToDo: Any others that match? Are we pulling in multiple
110     // already-rotated files? Algorithm thus far is KISS: one file,
111     // one rotation allowed.
112 
113     rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE);
114     rotated = true;
115 
116     return android::base::WriteStringToFile(buffer, destination.c_str());
117 }
118 
file_size(const char * path)119 size_t file_size(const char* path) {
120     struct stat st {};
121     if (stat(path, &st) < 0) {
122         return 0;
123     }
124 
125     return st.st_size;
126 }
127 
compare_file(const char * file1,const char * file2)128 bool compare_file(const char* file1, const char* file2) {
129     if (!file_exists(file1) || !file_exists(file2)) {
130         return false;
131     }
132     if (file_size(file1) != file_size(file2)) {
133         return false;
134     }
135     std::array<uint8_t, 1024 * 16> buf1{};
136     std::array<uint8_t, 1024 * 16> buf2{};
137     android::base::unique_fd fd1(open(file1, O_RDONLY));
138     android::base::unique_fd fd2(open(file2, O_RDONLY));
139     auto bytes_remain = file_size(file1);
140     while (bytes_remain > 0) {
141         const auto bytes_to_read = std::min<size_t>(bytes_remain, buf1.size());
142 
143         if (!android::base::ReadFully(fd1, buf1.data(), bytes_to_read)) {
144             LOG(ERROR) << "Failed to read from " << file1;
145             return false;
146         }
147         if (!android::base::ReadFully(fd2, buf2.data(), bytes_to_read)) {
148             LOG(ERROR) << "Failed to read from " << file2;
149             return false;
150         }
151         if (memcmp(buf1.data(), buf2.data(), bytes_to_read) != 0) {
152             return false;
153         }
154     }
155     return true;
156 }
157 
rotate_last_kmsg()158 void rotate_last_kmsg() {
159     if (rotated) {
160         return;
161     }
162     if (!file_exists(LAST_CONSOLE_FILE) && !file_exists(ALT_LAST_CONSOLE_FILE)) {
163         return;
164     }
165     if (!compare_file(LAST_KMSG_FILE, LAST_CONSOLE_FILE) &&
166         !compare_file(LAST_KMSG_FILE, ALT_LAST_CONSOLE_FILE)) {
167         rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE);
168         rotated = true;
169     }
170 }
171 
main(int argc,char ** argv)172 int main(int argc, char **argv) {
173 
174     /* Is /cache a mount?, we have been delivered where we are not wanted */
175     /*
176      * Following code halves the size of the executable as compared to:
177      *
178      *    load_volume_table();
179      *    has_cache = volume_for_path(CACHE_ROOT) != nullptr;
180      */
181     bool has_cache = false;
182     static const char mounts_file[] = "/proc/mounts";
183     FILE* fp = fopen(mounts_file, "re");
184     if (!fp) {
185         PLOG(ERROR) << "failed to open " << mounts_file;
186     } else {
187         char *line = NULL;
188         size_t len = 0;
189         ssize_t read{};
190         while ((read = getline(&line, &len, fp)) != -1) {
191             if (strstr(line, " /cache ")) {
192                 has_cache = true;
193                 break;
194             }
195         }
196         free(line);
197         fclose(fp);
198     }
199 
200     if (has_cache) {
201       // Collects and reports the non-a/b update metrics from last_install; and removes the file
202       // to avoid duplicate report.
203       if (file_exists(LAST_INSTALL_FILE_IN_CACHE) && unlink(LAST_INSTALL_FILE_IN_CACHE) == -1) {
204         PLOG(ERROR) << "Failed to unlink " << LAST_INSTALL_FILE_IN_CACHE;
205       }
206 
207       // TBD: Future location to move content from /cache/recovery to /data/misc/recovery/
208       // if --force-persist flag, then transfer pmsg data anyways
209       if ((argc <= 1) || !argv[1] || strcmp(argv[1], "--force-persist")) {
210         return 0;
211       }
212     }
213 
214     /* Is there something in pmsg? If not, no need to proceed. */
215     if (!file_exists(LAST_PMSG_FILE)) {
216       return 0;
217     }
218 
219     // Take last pmsg file contents and send it off to the logsave
220     __android_log_pmsg_file_read(
221         LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", logsave, NULL);
222 
223     // For those device without /cache, the last_install file has been copied to
224     // /data/misc/recovery from pmsg. Looks for the sideload history only.
225     if (!has_cache) {
226       if (file_exists(LAST_INSTALL_FILE) && unlink(LAST_INSTALL_FILE) == -1) {
227         PLOG(ERROR) << "Failed to unlink " << LAST_INSTALL_FILE;
228       }
229     }
230     rotate_last_kmsg();
231 
232     /* Is there a last console log too? */
233     if (rotated) {
234       if (file_exists(LAST_CONSOLE_FILE)) {
235         copy_file(LAST_CONSOLE_FILE, LAST_KMSG_FILE);
236       } else if (file_exists(ALT_LAST_CONSOLE_FILE)) {
237         copy_file(ALT_LAST_CONSOLE_FILE, LAST_KMSG_FILE);
238       }
239     }
240 
241     return 0;
242 }
243