blob: b39c2d5e59a861e2c4ee3e0d06f285de20ee6c6c [file] [log] [blame]
Xiaochu Liu6da17272018-11-06 14:04:01 -08001// Copyright 2018 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "dlcservice/utils.h"
6
Jae Hoon Kime7db33a2020-01-30 12:43:54 -08007#include <fcntl.h>
Jae Hoon Kima050ad72020-01-30 19:30:21 -08008#include <sys/types.h>
9#include <unistd.h>
Jae Hoon Kime7db33a2020-01-30 12:43:54 -080010
Jae Hoon Kima050ad72020-01-30 19:30:21 -080011#include <algorithm>
Jae Hoon Kim760a6942020-04-15 10:01:02 -070012#include <memory>
Jae Hoon Kim873404c2019-08-08 15:43:49 -070013#include <utility>
Jae Hoon Kim760a6942020-04-15 10:01:02 -070014#include <vector>
Jae Hoon Kim873404c2019-08-08 15:43:49 -070015
Amin Hassani69dfe6e2019-03-20 14:58:48 -070016#include <base/files/file_enumerator.h>
Colin Howes56ecef12018-12-11 09:55:39 -080017#include <base/files/file_util.h>
18#include <base/logging.h>
Jae Hoon Kim760a6942020-04-15 10:01:02 -070019#include <base/strings/string_number_conversions.h>
Jae Hoon Kime7db33a2020-01-30 12:43:54 -080020#include <brillo/file_utils.h>
Jae Hoon Kim760a6942020-04-15 10:01:02 -070021#include <crypto/secure_hash.h>
22#include <crypto/sha2.h>
Colin Howes56ecef12018-12-11 09:55:39 -080023
Jae Hoon Kim5d095de2019-07-31 13:44:42 -070024using base::FilePath;
Amin Hassani5bbbf212020-04-30 09:43:17 -070025using crypto::SecureHash;
Jae Hoon Kim5d095de2019-07-31 13:44:42 -070026using std::set;
27using std::string;
Jae Hoon Kim760a6942020-04-15 10:01:02 -070028using std::unique_ptr;
29using std::vector;
Jae Hoon Kim5d095de2019-07-31 13:44:42 -070030
Xiaochu Liu6da17272018-11-06 14:04:01 -080031namespace dlcservice {
32
Jae Hoon Kim074a9102020-01-29 17:38:27 -080033namespace {
34
35bool SetFilePermissions(const base::FilePath& path, int perms) {
Amin Hassanic0867b62020-04-16 09:44:34 -070036 // Do not try to set the permission if the permissions are already correct. If
37 // it failed to get the permissions, go ahead and set them.
38 int tmp_perms;
39 if (base::GetPosixFilePermissions(path, &tmp_perms) && perms == tmp_perms)
40 return true;
41
Jae Hoon Kim074a9102020-01-29 17:38:27 -080042 if (!base::SetPosixFilePermissions(path, perms)) {
43 PLOG(ERROR) << "Failed to set permissions for: " << path.value();
44 return false;
45 }
46 return true;
47}
48
Amin Hassani7c6543f2020-06-02 16:16:43 -070049bool WriteFile(const FilePath& path, const string& data, bool truncate) {
50 int flags = O_CREAT | O_WRONLY;
51 if (truncate)
52 flags |= O_TRUNC;
53
54 base::ScopedFD fd(brillo::OpenSafely(path, flags, kDlcFilePerms));
55 if (!fd.is_valid()) {
56 LOG(ERROR) << "Failed to open file for writting " << path.value();
57 return false;
58 }
59 if (data.empty())
60 return true;
61 return base::WriteFileDescriptor(fd.get(), data.c_str(), data.size());
62}
63
Jae Hoon Kim074a9102020-01-29 17:38:27 -080064} // namespace
65
Jae Hoon Kim9ecbeba2020-01-16 16:32:32 -080066char kDlcDirAName[] = "dlc_a";
67char kDlcDirBName[] = "dlc_b";
Jae Hoon Kim9ecbeba2020-01-16 16:32:32 -080068
69char kDlcImageFileName[] = "dlc.img";
70char kManifestName[] = "imageloader.json";
71
72char kRootDirectoryInsideDlcModule[] = "root";
73
Jae Hoon Kim074a9102020-01-29 17:38:27 -080074const int kDlcFilePerms = 0644;
75const int kDlcDirectoryPerms = 0755;
Jae Hoon Kim373cd652020-01-21 10:03:39 -080076
Jae Hoon Kime7db33a2020-01-30 12:43:54 -080077bool WriteToFile(const FilePath& path, const string& data) {
Amin Hassani7c6543f2020-06-02 16:16:43 -070078 return WriteFile(path, data, /*truncate=*/true);
Jae Hoon Kime7db33a2020-01-30 12:43:54 -080079}
80
Amin Hassani7c6543f2020-06-02 16:16:43 -070081bool WriteToImage(const FilePath& path, const string& data) {
82 return WriteFile(path, data, /*truncate=*/false);
83}
84
Jae Hoon Kima050ad72020-01-30 19:30:21 -080085bool ResizeFile(const base::FilePath& path, int64_t size) {
86 int64_t prev_size;
87 base::File f(path, base::File::FLAG_OPEN | base::File::FLAG_WRITE);
88 if (!f.IsValid()) {
89 LOG(ERROR) << "Failed to open file to resize '" << path.value()
90 << "': " << base::File::ErrorToString(f.error_details());
91 return false;
92 }
93 prev_size = f.GetLength();
94 if (prev_size < 0) {
95 PLOG(ERROR) << "Failed to get file size for resizing " << path.value();
96 return false;
97 }
98 if (!f.SetLength(size)) {
Tom Hughesff483652020-08-24 17:58:00 -070099 PLOG(ERROR) << "Failed to set length (" << size << ") for " << path.value();
Jae Hoon Kima050ad72020-01-30 19:30:21 -0800100 return false;
101 }
102 // When shrinking files, there is no need to unsparse as it's not certainly
103 // safe to unsparse potentially used portions of the file.
104 if (size <= prev_size)
105 return true;
106
107 // Otherwise, unsparse the increased portion of the file.
108 if (f.Seek(base::File::Whence::FROM_BEGIN, prev_size) < 0) {
109 PLOG(ERROR) << "Failed to lseek() to offset " << prev_size << " for "
110 << path.value();
111 return false;
112 }
113 size -= prev_size;
114
115 constexpr int64_t kMaxBufSize = 4096;
116 constexpr char buf[kMaxBufSize] = {'\0'};
117 for (; size > 0; size -= kMaxBufSize) {
118 // Set the lesser of either |kMaxBufSize| or |size| bytes.
119 const size_t len = std::min(size, kMaxBufSize);
120 // Write out |len| from |buf| to |fd|.
121 if (f.WriteAtCurrentPos(buf, len) != len) {
122 PLOG(ERROR) << "Failed to write zero to " << path.value();
123 return false;
124 }
125 }
126 return true;
127}
128
Jae Hoon Kim074a9102020-01-29 17:38:27 -0800129bool CreateDir(const base::FilePath& path) {
Jae Hoon Kim373cd652020-01-21 10:03:39 -0800130 base::File::Error file_err;
131 if (!base::CreateDirectoryAndGetError(path, &file_err)) {
Amin Hassania69f32e2020-03-30 15:20:42 -0700132 PLOG(ERROR) << "Failed to create directory '" << path.value()
133 << "': " << base::File::ErrorToString(file_err);
Jae Hoon Kim373cd652020-01-21 10:03:39 -0800134 return false;
135 }
Jae Hoon Kim074a9102020-01-29 17:38:27 -0800136 return SetFilePermissions(path, kDlcDirectoryPerms);
Amin Hassani69dfe6e2019-03-20 14:58:48 -0700137}
138
Jae Hoon Kima050ad72020-01-30 19:30:21 -0800139// TODO(crbug.com/976074): When creating a file, provide the flexibility to be
140// able to unsparse in |ResizeFile()| up to the actual size necessary and not
141// the preallocated size from the manifest as is the |size| here for DLC to
142// install successfully.
Jae Hoon Kim373cd652020-01-21 10:03:39 -0800143bool CreateFile(const base::FilePath& path, int64_t size) {
Jae Hoon Kim074a9102020-01-29 17:38:27 -0800144 if (!CreateDir(path.DirName()))
Jae Hoon Kim373cd652020-01-21 10:03:39 -0800145 return false;
Jae Hoon Kima050ad72020-01-30 19:30:21 -0800146 // Keep scoped to not explicitly close file.
147 {
Amin Hassanic0867b62020-04-16 09:44:34 -0700148 base::File f(path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_WRITE);
Jae Hoon Kima050ad72020-01-30 19:30:21 -0800149 if (!f.IsValid()) {
150 LOG(ERROR) << "Failed to create file at " << path.value()
151 << " reason: " << base::File::ErrorToString(f.error_details());
152 return false;
153 }
Jae Hoon Kim373cd652020-01-21 10:03:39 -0800154 }
Jae Hoon Kima050ad72020-01-30 19:30:21 -0800155 return ResizeFile(path, size) && SetFilePermissions(path, kDlcFilePerms);
Jae Hoon Kim373cd652020-01-21 10:03:39 -0800156}
157
Amin Hassanif2efc5a2020-05-25 21:22:57 -0700158bool HashFile(const base::FilePath& path,
159 int64_t size,
160 vector<uint8_t>* sha256) {
Jae Hoon Kim8ab26492020-05-07 13:49:36 -0700161 base::File f(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
162 if (!f.IsValid()) {
163 PLOG(ERROR) << "Failed to read file at " << path.value()
164 << ", reason: " << base::File::ErrorToString(f.error_details());
165 return false;
166 }
167
168 auto length = f.GetLength();
169 if (length < 0) {
170 LOG(ERROR) << "Failed to get length for file at " << path.value();
171 return false;
172 }
Amin Hassanif2efc5a2020-05-25 21:22:57 -0700173 if (length < size) {
174 LOG(ERROR) << "File size " << length
175 << " is smaller than intended file size " << size;
176 return false;
177 }
Jae Hoon Kim8ab26492020-05-07 13:49:36 -0700178
179 constexpr int64_t kMaxBufSize = 4096;
180 unique_ptr<SecureHash> hash(SecureHash::Create(SecureHash::SHA256));
181
182 vector<char> buf(kMaxBufSize);
Amin Hassanif2efc5a2020-05-25 21:22:57 -0700183 for (; size > 0; size -= kMaxBufSize) {
184 int bytes = std::min(kMaxBufSize, size);
Jae Hoon Kim8ab26492020-05-07 13:49:36 -0700185 if (f.ReadAtCurrentPos(buf.data(), bytes) != bytes) {
186 PLOG(ERROR) << "Failed to read from file at " << path.value();
187 return false;
188 }
189 hash->Update(buf.data(), bytes);
190 }
191 sha256->resize(crypto::kSHA256Length);
192 hash->Finish(sha256->data(), sha256->size());
193 return true;
194}
195
Jae Hoon Kim760a6942020-04-15 10:01:02 -0700196bool CopyAndHashFile(const base::FilePath& from,
197 const base::FilePath& to,
Amin Hassanif2efc5a2020-05-25 21:22:57 -0700198 int64_t size,
Amin Hassani0d3ab932020-04-26 17:35:04 -0700199 vector<uint8_t>* sha256) {
Jae Hoon Kim760a6942020-04-15 10:01:02 -0700200 base::File f_from(from, base::File::FLAG_OPEN | base::File::FLAG_READ);
201 if (!f_from.IsValid()) {
202 PLOG(ERROR) << "Failed to read file at " << from.value() << " reason: "
203 << base::File::ErrorToString(f_from.error_details());
Jae Hoon Kim373cd652020-01-21 10:03:39 -0800204 return false;
205 }
Jae Hoon Kim760a6942020-04-15 10:01:02 -0700206 base::File f_to(to, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_WRITE);
207 if (!f_to.IsValid()) {
208 PLOG(ERROR) << "Failed to open file at " << to.value() << " reason: "
209 << base::File::ErrorToString(f_to.error_details());
210 return false;
211 }
212
213 auto from_length = f_from.GetLength();
214 if (from_length < 0) {
215 LOG(ERROR) << "Failed to get length for file at " << from.value();
216 return false;
217 }
Amin Hassanif2efc5a2020-05-25 21:22:57 -0700218 if (from_length < size) {
219 LOG(ERROR) << "Preloaded file size " << from_length
220 << " is smaller than intended file size " << size;
221 return false;
222 }
Jae Hoon Kim760a6942020-04-15 10:01:02 -0700223
224 constexpr int64_t kMaxBufSize = 4096;
Amin Hassani5bbbf212020-04-30 09:43:17 -0700225 unique_ptr<SecureHash> hash(SecureHash::Create(SecureHash::SHA256));
Jae Hoon Kim760a6942020-04-15 10:01:02 -0700226
227 vector<char> buf(kMaxBufSize);
Amin Hassanif2efc5a2020-05-25 21:22:57 -0700228 for (; size > 0; size -= kMaxBufSize) {
229 int bytes = std::min(kMaxBufSize, size);
Jae Hoon Kim760a6942020-04-15 10:01:02 -0700230 if (f_from.ReadAtCurrentPos(buf.data(), bytes) != bytes) {
231 PLOG(ERROR) << "Failed to read from file at " << from.value();
232 return false;
233 }
234 if (f_to.WriteAtCurrentPos(buf.data(), bytes) != bytes) {
235 PLOG(ERROR) << "Failed to write to file at " << from.value();
236 return false;
237 }
238 hash->Update(buf.data(), bytes);
239 }
Amin Hassani0d3ab932020-04-26 17:35:04 -0700240 sha256->resize(crypto::kSHA256Length);
241 hash->Finish(sha256->data(), sha256->size());
Jae Hoon Kim760a6942020-04-15 10:01:02 -0700242
Jae Hoon Kim760a6942020-04-15 10:01:02 -0700243 return SetFilePermissions(to, kDlcFilePerms);
Xiaochu Liu6da17272018-11-06 14:04:01 -0800244}
245
Jae Hoon Kimace79b72020-03-10 18:00:15 +0000246FilePath GetDlcImagePath(const FilePath& dlc_module_root_path,
247 const string& id,
248 const string& package,
249 BootSlot::Slot slot) {
Jae Hoon Kime567b9e2020-04-08 14:43:59 -0700250 return JoinPaths(dlc_module_root_path, id, package, BootSlot::ToString(slot),
251 kDlcImageFileName);
Colin Howes56ecef12018-12-11 09:55:39 -0800252}
253
254// Extract details about a DLC module from its manifest file.
Jae Hoon Kimace79b72020-03-10 18:00:15 +0000255bool GetDlcManifest(const FilePath& dlc_manifest_path,
256 const string& id,
257 const string& package,
258 imageloader::Manifest* manifest_out) {
Jae Hoon Kim5d095de2019-07-31 13:44:42 -0700259 string dlc_json_str;
260 FilePath dlc_manifest_file =
Jae Hoon Kim373cd652020-01-21 10:03:39 -0800261 JoinPaths(dlc_manifest_path, id, package, kManifestName);
Colin Howes56ecef12018-12-11 09:55:39 -0800262
263 if (!base::ReadFileToString(dlc_manifest_file, &dlc_json_str)) {
264 LOG(ERROR) << "Failed to read DLC manifest file '"
265 << dlc_manifest_file.value() << "'.";
266 return false;
267 }
268
269 if (!manifest_out->ParseManifest(dlc_json_str)) {
Andrew6a55cf42020-01-02 15:01:36 -0800270 LOG(ERROR) << "Failed to parse DLC manifest for DLC:" << id << ".";
Colin Howes56ecef12018-12-11 09:55:39 -0800271 return false;
272 }
273
274 return true;
Xiaochu Liu6da17272018-11-06 14:04:01 -0800275}
276
Jae Hoon Kim5d095de2019-07-31 13:44:42 -0700277set<string> ScanDirectory(const FilePath& dir) {
278 set<string> result;
Amin Hassani69dfe6e2019-03-20 14:58:48 -0700279 base::FileEnumerator file_enumerator(dir, false,
280 base::FileEnumerator::DIRECTORIES);
Jae Hoon Kim5d095de2019-07-31 13:44:42 -0700281 for (FilePath dir_path = file_enumerator.Next(); !dir_path.empty();
Amin Hassani69dfe6e2019-03-20 14:58:48 -0700282 dir_path = file_enumerator.Next()) {
Jae Hoon Kim013d58a2019-07-15 16:12:05 -0700283 result.emplace(dir_path.BaseName().value());
Amin Hassani69dfe6e2019-03-20 14:58:48 -0700284 }
285 return result;
286}
287
Xiaochu Liu6da17272018-11-06 14:04:01 -0800288} // namespace dlcservice