blob: f9b6869fc8314115539512e08d957b980b7f31ad [file] [log] [blame]
adlr@google.com3defe6a2009-12-04 20:57:17 +00001// Copyright (c) 2009 The Chromium 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 "update_engine/filesystem_copier_action.h"
6#include <sys/stat.h>
7#include <sys/types.h>
8#include <errno.h>
9#include <fcntl.h>
10#include <stdlib.h>
11#include <algorithm>
Andrew de los Reyes4fe15d02009-12-10 19:01:36 -080012#include <map>
adlr@google.com3defe6a2009-12-04 20:57:17 +000013#include <string>
14#include <vector>
15#include "update_engine/filesystem_iterator.h"
16#include "update_engine/subprocess.h"
17#include "update_engine/utils.h"
18
Andrew de los Reyes4fe15d02009-12-10 19:01:36 -080019using std::map;
adlr@google.com3defe6a2009-12-04 20:57:17 +000020using std::min;
21using std::string;
22using std::vector;
23
24namespace chromeos_update_engine {
25
26namespace {
27const char* kMountpointTemplate = "/tmp/au_dest_mnt.XXXXXX";
28const off_t kCopyFileBufferSize = 4 * 1024 * 1024;
29const char* kCopyExclusionPrefix = "/lost+found";
30} // namespace {}
31
32void FilesystemCopierAction::PerformAction() {
33 if (!HasInputObject()) {
34 LOG(ERROR) << "No input object. Aborting.";
35 processor_->ActionComplete(this, false);
36 return;
37 }
38 install_plan_ = GetInputObject();
39
40 if (install_plan_.is_full_update) {
41 // No copy needed.
42 processor_->ActionComplete(this, true);
43 return;
44 }
45
46 {
47 // Set up dest_path_
48 char *dest_path_temp = strdup(kMountpointTemplate);
49 CHECK(dest_path_temp);
50 CHECK_EQ(mkdtemp(dest_path_temp), dest_path_temp);
51 CHECK_NE(dest_path_temp[0], '\0');
52 dest_path_ = dest_path_temp;
53 free(dest_path_temp);
54 }
55
56 // Make sure we're properly mounted
57 if (Mount(install_plan_.install_path, dest_path_)) {
58 bool done_early = false;
59 if (utils::FileExists(
60 (dest_path_ +
61 FilesystemCopierAction::kCompleteFilesystemMarker).c_str())) {
62 // We're done!
63 done_early = true;
64 skipped_copy_ = true;
65 if (HasOutputPipe())
66 SetOutputObject(install_plan_);
67 }
68 if (!Unmount(dest_path_)) {
69 LOG(ERROR) << "Unmount failed. Aborting.";
70 processor_->ActionComplete(this, false);
71 return;
72 }
73 if (done_early) {
74 CHECK(!is_mounted_);
75 if (rmdir(dest_path_.c_str()) != 0)
76 LOG(ERROR) << "Unable to remove " << dest_path_;
77 processor_->ActionComplete(this, true);
78 return;
79 }
80 }
81 LOG(ERROR) << "not mounted; spawning thread";
82 // If we get here, mount failed or we're not done yet. Reformat and copy.
83 CHECK_EQ(pthread_create(&helper_thread_, NULL, HelperThreadMainStatic, this),
84 0);
85}
86
87void FilesystemCopierAction::TerminateProcessing() {
88 if (is_mounted_) {
89 LOG(ERROR) << "Aborted processing, but left a filesystem mounted.";
90 }
91}
92
93bool FilesystemCopierAction::Mount(const string& device,
94 const string& mountpoint) {
95 CHECK(!is_mounted_);
96 if(utils::MountFilesystem(device, mountpoint))
97 is_mounted_ = true;
98 return is_mounted_;
99}
100
101bool FilesystemCopierAction::Unmount(const string& mountpoint) {
102 CHECK(is_mounted_);
103 if (utils::UnmountFilesystem(mountpoint))
104 is_mounted_ = false;
105 return !is_mounted_;
106}
107
108void* FilesystemCopierAction::HelperThreadMain() {
109 // First, format the drive
110 vector<string> cmd;
111 cmd.push_back("/sbin/mkfs.ext3");
112 cmd.push_back("-F");
113 cmd.push_back(install_plan_.install_path);
114 int return_code = 1;
115 bool success = Subprocess::SynchronousExec(cmd, &return_code);
116 if (return_code != 0) {
117 LOG(INFO) << "Format of " << install_plan_.install_path
118 << " failed. Exit code: " << return_code;
119 success = false;
120 }
121 if (success) {
122 if (!Mount(install_plan_.install_path, dest_path_)) {
123 LOG(ERROR) << "Mount failed. Aborting";
124 success = false;
125 }
126 }
127 if (success) {
128 success = CopySynchronously();
129 }
130 if (success) {
131 // Place our marker to avoid copies again in the future
132 int r = open((dest_path_ +
133 FilesystemCopierAction::kCompleteFilesystemMarker).c_str(),
134 O_CREAT | O_WRONLY, 0644);
135 if (r >= 0)
136 close(r);
137 }
138 // Unmount
139 if (!Unmount(dest_path_)) {
140 LOG(ERROR) << "Unmount failed. Aborting";
141 success = false;
142 }
143 if (HasOutputPipe())
144 SetOutputObject(install_plan_);
145
146 // Tell main thread that we're done
147 g_timeout_add(0, CollectThreadStatic, this);
148 return reinterpret_cast<void*>(success ? 0 : 1);
149}
150
151void FilesystemCopierAction::CollectThread() {
152 void *thread_ret_value = NULL;
153 CHECK_EQ(pthread_join(helper_thread_, &thread_ret_value), 0);
154 bool success = (thread_ret_value == 0);
155 CHECK(!is_mounted_);
156 if (rmdir(dest_path_.c_str()) != 0)
157 LOG(INFO) << "Unable to remove " << dest_path_;
158 LOG(INFO) << "FilesystemCopierAction done";
159 processor_->ActionComplete(this, success);
160}
161
162bool FilesystemCopierAction::CreateDirSynchronously(const std::string& new_path,
163 const struct stat& stbuf) {
164 int r = mkdir(new_path.c_str(), stbuf.st_mode);
165 TEST_AND_RETURN_FALSE_ERRNO(r == 0);
166 return true;
167}
168
169bool FilesystemCopierAction::CopyFileSynchronously(const std::string& old_path,
170 const std::string& new_path,
171 const struct stat& stbuf) {
172 int fd_out = open(new_path.c_str(), O_CREAT | O_EXCL | O_WRONLY,
173 stbuf.st_mode);
174 TEST_AND_RETURN_FALSE_ERRNO(fd_out >= 0);
175 ScopedFdCloser fd_out_closer(&fd_out);
176 int fd_in = open(old_path.c_str(), O_RDONLY, 0);
177 TEST_AND_RETURN_FALSE_ERRNO(fd_in >= 0);
178 ScopedFdCloser fd_in_closer(&fd_in);
179
180 vector<char> buf(min(kCopyFileBufferSize, stbuf.st_size));
181 off_t bytes_written = 0;
182 while (true) {
183 // Make sure we don't need to abort early:
184 TEST_AND_RETURN_FALSE(!g_atomic_int_get(&thread_should_exit_));
185
186 ssize_t read_size = read(fd_in, &buf[0], buf.size());
187 TEST_AND_RETURN_FALSE_ERRNO(read_size >= 0);
188 if (0 == read_size) // EOF
189 break;
190
191 ssize_t write_size = 0;
192 while (write_size < read_size) {
193 ssize_t r = write(fd_out, &buf[write_size], read_size - write_size);
194 TEST_AND_RETURN_FALSE_ERRNO(r >= 0);
195 write_size += r;
196 }
197 CHECK_EQ(write_size, read_size);
198 bytes_written += write_size;
199 CHECK_LE(bytes_written, stbuf.st_size);
200 if (bytes_written == stbuf.st_size)
201 break;
202 }
203 CHECK_EQ(bytes_written, stbuf.st_size);
204 return true;
205}
206
207bool FilesystemCopierAction::CreateHardLinkSynchronously(
208 const std::string& old_path,
209 const std::string& new_path) {
210 int r = link(old_path.c_str(), new_path.c_str());
211 TEST_AND_RETURN_FALSE_ERRNO(r == 0);
212 return true;
213}
214
215bool FilesystemCopierAction::CopySymlinkSynchronously(
216 const std::string& old_path,
217 const std::string& new_path,
218 const struct stat& stbuf) {
219 vector<char> buf(PATH_MAX + 1);
220 ssize_t r = readlink(old_path.c_str(), &buf[0], buf.size());
221 TEST_AND_RETURN_FALSE_ERRNO(r >= 0);
222 // Make sure we got the entire link
223 TEST_AND_RETURN_FALSE(static_cast<unsigned>(r) < buf.size());
224 buf[r] = '\0';
225 int rc = symlink(&buf[0], new_path.c_str());
226 TEST_AND_RETURN_FALSE_ERRNO(rc == 0);
227 return true;
228}
229
230bool FilesystemCopierAction::CreateNodeSynchronously(
231 const std::string& new_path,
232 const struct stat& stbuf) {
233 int r = mknod(new_path.c_str(), stbuf.st_mode, stbuf.st_rdev);
234 TEST_AND_RETURN_FALSE_ERRNO(r == 0);
235 return true;
236}
237
238// Returns true on success
239bool FilesystemCopierAction::CopySynchronously() {
240 // This map is a map from inode # to new_path.
241 map<ino_t, string> hard_links;
242 FilesystemIterator iter(copy_source_,
243 utils::SetWithValue<string>(kCopyExclusionPrefix));
244 bool success = true;
245 for (; !g_atomic_int_get(&thread_should_exit_) &&
246 !iter.IsEnd(); iter.Increment()) {
247 const string old_path = iter.GetFullPath();
248 const string new_path = dest_path_ + iter.GetPartialPath();
249 LOG(INFO) << "copying " << old_path << " to " << new_path;
250 const struct stat stbuf = iter.GetStat();
251 success = false;
252
253 // Skip lost+found
254 CHECK_NE(kCopyExclusionPrefix, iter.GetPartialPath());
255
256 // Directories can't be hard-linked, so check for directories first
257 if (iter.GetPartialPath().empty()) {
258 // Root has an empty path.
259 // We don't need to create anything for the root, which is the first
260 // thing we get from the iterator.
261 success = true;
262 } else if (S_ISDIR(stbuf.st_mode)) {
263 success = CreateDirSynchronously(new_path, stbuf);
264 } else {
265 if (stbuf.st_nlink > 1 &&
266 utils::MapContainsKey(hard_links, stbuf.st_ino)) {
267 success = CreateHardLinkSynchronously(hard_links[stbuf.st_ino],
268 new_path);
269 } else {
270 if (stbuf.st_nlink > 1)
271 hard_links[stbuf.st_ino] = new_path;
272 if (S_ISREG(stbuf.st_mode)) {
273 success = CopyFileSynchronously(old_path, new_path, stbuf);
274 } else if (S_ISLNK(stbuf.st_mode)) {
275 success = CopySymlinkSynchronously(old_path, new_path, stbuf);
276 } else if (S_ISFIFO(stbuf.st_mode) ||
277 S_ISCHR(stbuf.st_mode) ||
278 S_ISBLK(stbuf.st_mode) ||
279 S_ISSOCK(stbuf.st_mode)) {
280 success = CreateNodeSynchronously(new_path, stbuf);
281 } else {
282 CHECK(false) << "Unable to copy file " << old_path << " with mode "
283 << stbuf.st_mode;
284 }
285 }
286 }
287 TEST_AND_RETURN_FALSE(success);
288
289 // chmod new file
290 if (!S_ISLNK(stbuf.st_mode)) {
291 int r = chmod(new_path.c_str(), stbuf.st_mode);
292 TEST_AND_RETURN_FALSE_ERRNO(r == 0);
293 }
294
295 // Set uid/gid.
296 int r = lchown(new_path.c_str(), stbuf.st_uid, stbuf.st_gid);
297 TEST_AND_RETURN_FALSE_ERRNO(r == 0);
298 }
299 TEST_AND_RETURN_FALSE(!iter.IsErr());
300 // Success!
301 return true;
302}
303
304const char* FilesystemCopierAction::kCompleteFilesystemMarker(
305 "/update_engine_copy_success");
306
307} // namespace chromeos_update_engine