1 /*
2  * Copyright (C) 2017 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 #include <algorithm>
18 #include <iterator>
19 #include <string>
20 
21 #include <android-base/file.h>
22 #include <android-base/properties.h>
23 #include <android-base/strings.h>
24 
25 #include "fstab_priv.h"
26 #include "logging_macros.h"
27 
28 namespace android {
29 namespace fs_mgr {
30 
GetAndroidDtDir()31 const std::string& GetAndroidDtDir() {
32     // Set once and saves time for subsequent calls to this function
33     static const std::string kAndroidDtDir = [] {
34         std::string android_dt_dir;
35         if ((GetBootconfig("androidboot.android_dt_dir", &android_dt_dir) ||
36              GetKernelCmdline("androidboot.android_dt_dir", &android_dt_dir)) &&
37             !android_dt_dir.empty()) {
38             // Ensure the returned path ends with a /
39             if (android_dt_dir.back() != '/') {
40                 android_dt_dir.push_back('/');
41             }
42         } else {
43             // Fall back to the standard procfs-based path
44             android_dt_dir = "/proc/device-tree/firmware/android/";
45         }
46         LINFO << "Using Android DT directory " << android_dt_dir;
47         return android_dt_dir;
48     }();
49     return kAndroidDtDir;
50 }
51 
ImportBootconfigFromString(const std::string & bootconfig,const std::function<void (std::string,std::string)> & fn)52 void ImportBootconfigFromString(const std::string& bootconfig,
53                                 const std::function<void(std::string, std::string)>& fn) {
54     for (std::string_view line : android::base::Split(bootconfig, "\n")) {
55         const auto equal_pos = line.find('=');
56         std::string key = android::base::Trim(line.substr(0, equal_pos));
57         if (key.empty()) {
58             continue;
59         }
60         std::string value;
61         if (equal_pos != line.npos) {
62             value = android::base::Trim(line.substr(equal_pos + 1));
63             // If the value is a comma-delimited list, the kernel would insert a space between the
64             // list elements when read from /proc/bootconfig.
65             // BoardConfig.mk:
66             //      BOARD_BOOTCONFIG := key=value1,value2,value3
67             // /proc/bootconfig:
68             //      key = "value1", "value2", "value3"
69             if (key == "androidboot.boot_device" || key == "androidboot.boot_devices") {
70                 // boot_device[s] is a special case where a list element can contain comma and the
71                 // caller expects a space-delimited list, so don't remove space here.
72                 value.erase(std::remove(value.begin(), value.end(), '"'), value.end());
73             } else {
74                 // In order to not break the expectations of existing code, we modify the value to
75                 // keep the format consistent with the kernel cmdline by removing quote and space.
76                 std::string_view sv(value);
77                 android::base::ConsumePrefix(&sv, "\"");
78                 android::base::ConsumeSuffix(&sv, "\"");
79                 value = android::base::StringReplace(sv, R"(", ")", ",", true);
80             }
81         }
82         // "key" and "key =" means empty value.
83         fn(std::move(key), std::move(value));
84     }
85 }
86 
GetBootconfigFromString(const std::string & bootconfig,const std::string & key,std::string * out)87 bool GetBootconfigFromString(const std::string& bootconfig, const std::string& key,
88                              std::string* out) {
89     bool found = false;
90     ImportBootconfigFromString(bootconfig, [&](std::string config_key, std::string value) {
91         if (!found && config_key == key) {
92             *out = std::move(value);
93             found = true;
94         }
95     });
96     return found;
97 }
98 
ImportBootconfig(const std::function<void (std::string,std::string)> & fn)99 void ImportBootconfig(const std::function<void(std::string, std::string)>& fn) {
100     std::string bootconfig;
101     android::base::ReadFileToString("/proc/bootconfig", &bootconfig);
102     ImportBootconfigFromString(bootconfig, fn);
103 }
104 
GetBootconfig(const std::string & key,std::string * out)105 bool GetBootconfig(const std::string& key, std::string* out) {
106     std::string bootconfig;
107     android::base::ReadFileToString("/proc/bootconfig", &bootconfig);
108     return GetBootconfigFromString(bootconfig, key, out);
109 }
110 
ImportKernelCmdlineFromString(const std::string & cmdline,const std::function<void (std::string,std::string)> & fn)111 void ImportKernelCmdlineFromString(const std::string& cmdline,
112                                    const std::function<void(std::string, std::string)>& fn) {
113     static constexpr char quote = '"';
114 
115     size_t base = 0;
116     while (true) {
117         // skip quoted spans
118         auto found = base;
119         while (((found = cmdline.find_first_of(" \"", found)) != cmdline.npos) &&
120                (cmdline[found] == quote)) {
121             // unbalanced quote is ok
122             if ((found = cmdline.find(quote, found + 1)) == cmdline.npos) break;
123             ++found;
124         }
125         std::string piece = cmdline.substr(base, found - base);
126         piece.erase(std::remove(piece.begin(), piece.end(), quote), piece.end());
127         auto equal_sign = piece.find('=');
128         if (equal_sign == piece.npos) {
129             if (!piece.empty()) {
130                 // no difference between <key> and <key>=
131                 fn(std::move(piece), "");
132             }
133         } else {
134             std::string value = piece.substr(equal_sign + 1);
135             piece.resize(equal_sign);
136             fn(std::move(piece), std::move(value));
137         }
138         if (found == cmdline.npos) break;
139         base = found + 1;
140     }
141 }
142 
GetKernelCmdlineFromString(const std::string & cmdline,const std::string & key,std::string * out)143 bool GetKernelCmdlineFromString(const std::string& cmdline, const std::string& key,
144                                 std::string* out) {
145     bool found = false;
146     ImportKernelCmdlineFromString(cmdline, [&](std::string config_key, std::string value) {
147         if (!found && config_key == key) {
148             *out = std::move(value);
149             found = true;
150         }
151     });
152     return found;
153 }
154 
ImportKernelCmdline(const std::function<void (std::string,std::string)> & fn)155 void ImportKernelCmdline(const std::function<void(std::string, std::string)>& fn) {
156     std::string cmdline;
157     android::base::ReadFileToString("/proc/cmdline", &cmdline);
158     ImportKernelCmdlineFromString(android::base::Trim(cmdline), fn);
159 }
160 
GetKernelCmdline(const std::string & key,std::string * out)161 bool GetKernelCmdline(const std::string& key, std::string* out) {
162     std::string cmdline;
163     android::base::ReadFileToString("/proc/cmdline", &cmdline);
164     return GetKernelCmdlineFromString(android::base::Trim(cmdline), key, out);
165 }
166 
167 }  // namespace fs_mgr
168 }  // namespace android
169 
170 // Tries to get the boot config value in device tree, properties, kernel bootconfig and kernel
171 // cmdline (in that order).
172 // Returns 'true' if successfully found, 'false' otherwise.
fs_mgr_get_boot_config(const std::string & key,std::string * out_val)173 bool fs_mgr_get_boot_config(const std::string& key, std::string* out_val) {
174     FSTAB_CHECK(out_val != nullptr);
175 
176     // firstly, check the device tree
177     if (is_dt_compatible()) {
178         std::string file_name = android::fs_mgr::GetAndroidDtDir() + key;
179         if (android::base::ReadFileToString(file_name, out_val)) {
180             if (!out_val->empty()) {
181                 out_val->pop_back();  // Trims the trailing '\0' out.
182                 return true;
183             }
184         }
185     }
186 
187     // next, check if we have "ro.boot" property already
188     *out_val = android::base::GetProperty("ro.boot." + key, "");
189     if (!out_val->empty()) {
190         return true;
191     }
192 
193     // next, check if we have the property in bootconfig
194     const std::string config_key = "androidboot." + key;
195     if (android::fs_mgr::GetBootconfig(config_key, out_val)) {
196         return true;
197     }
198 
199     // finally, fallback to kernel cmdline, properties may not be ready yet
200     if (android::fs_mgr::GetKernelCmdline(config_key, out_val)) {
201         return true;
202     }
203 
204     return false;
205 }
206