OLD | NEW |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "update_engine/filesystem_copier_action.h" | 5 #include "update_engine/filesystem_copier_action.h" |
6 #include <sys/stat.h> | 6 #include <sys/stat.h> |
7 #include <sys/types.h> | 7 #include <sys/types.h> |
8 #include <errno.h> | 8 #include <errno.h> |
9 #include <fcntl.h> | 9 #include <fcntl.h> |
10 #include <stdlib.h> | 10 #include <stdlib.h> |
11 #include <algorithm> | 11 #include <algorithm> |
12 #include <map> | 12 #include <map> |
13 #include <string> | 13 #include <string> |
14 #include <vector> | 14 #include <vector> |
| 15 #include <gio/gio.h> |
| 16 #include <gio/gunixinputstream.h> |
| 17 #include <gio/gunixoutputstream.h> |
| 18 #include <glib.h> |
15 #include "update_engine/filesystem_iterator.h" | 19 #include "update_engine/filesystem_iterator.h" |
16 #include "update_engine/subprocess.h" | 20 #include "update_engine/subprocess.h" |
17 #include "update_engine/utils.h" | 21 #include "update_engine/utils.h" |
18 | 22 |
19 using std::map; | 23 using std::map; |
20 using std::min; | 24 using std::min; |
21 using std::string; | 25 using std::string; |
22 using std::vector; | 26 using std::vector; |
23 | 27 |
24 namespace chromeos_update_engine { | 28 namespace chromeos_update_engine { |
25 | 29 |
26 namespace { | 30 namespace { |
27 const char* kMountpointTemplate = "/tmp/au_dest_mnt.XXXXXX"; | 31 const off_t kCopyFileBufferSize = 2 * 1024 * 1024; |
28 const off_t kCopyFileBufferSize = 4 * 1024 * 1024; | |
29 const char* kCopyExclusionPrefix = "/lost+found"; | |
30 } // namespace {} | 32 } // namespace {} |
31 | 33 |
32 void FilesystemCopierAction::PerformAction() { | 34 void FilesystemCopierAction::PerformAction() { |
| 35 // Will tell the ActionProcessor we've failed if we return. |
| 36 ScopedActionCompleter abort_action_completer(processor_, this); |
| 37 |
33 if (!HasInputObject()) { | 38 if (!HasInputObject()) { |
34 LOG(ERROR) << "No input object. Aborting."; | 39 LOG(ERROR) << "FilesystemCopierAction missing input object."; |
35 processor_->ActionComplete(this, false); | |
36 return; | 40 return; |
37 } | 41 } |
38 install_plan_ = GetInputObject(); | 42 install_plan_ = GetInputObject(); |
39 | 43 |
40 if (install_plan_.is_full_update) { | 44 if (install_plan_.is_full_update) { |
41 // No copy needed. | 45 // No copy needed. Done! |
42 processor_->ActionComplete(this, true); | 46 abort_action_completer.set_success(true); |
43 return; | 47 return; |
44 } | 48 } |
45 | 49 |
46 { | 50 const string source = |
47 // Set up dest_path_ | 51 copy_source_.empty() ? utils::BootDevice() : copy_source_; |
48 char *dest_path_temp = strdup(kMountpointTemplate); | 52 LOG(INFO) << "Copying from " << source << " to " |
49 CHECK(dest_path_temp); | 53 << install_plan_.install_path; |
50 CHECK_EQ(mkdtemp(dest_path_temp), dest_path_temp); | 54 |
51 CHECK_NE(dest_path_temp[0], '\0'); | 55 int src_fd = open(source.c_str(), O_RDONLY); |
52 dest_path_ = dest_path_temp; | 56 if (src_fd < 0) { |
53 free(dest_path_temp); | 57 PLOG(ERROR) << "Unable to open " << source << " for reading:"; |
| 58 return; |
| 59 } |
| 60 int dst_fd = open(install_plan_.install_path.c_str(), |
| 61 O_WRONLY | O_TRUNC | O_CREAT, |
| 62 0644); |
| 63 if (dst_fd < 0) { |
| 64 close(src_fd); |
| 65 PLOG(ERROR) << "Unable to open " << install_plan_.install_path |
| 66 << " for writing:"; |
| 67 return; |
54 } | 68 } |
55 | 69 |
56 // Make sure we're properly mounted | 70 src_stream_ = g_unix_input_stream_new(src_fd, TRUE); |
57 if (Mount(install_plan_.install_path, dest_path_)) { | 71 dst_stream_ = g_unix_output_stream_new(dst_fd, TRUE); |
58 bool done_early = false; | 72 |
59 if (utils::FileExists( | 73 buffer_.resize(kCopyFileBufferSize); |
60 (dest_path_ + | 74 |
61 FilesystemCopierAction::kCompleteFilesystemMarker).c_str())) { | 75 // Set up the first read |
62 // We're done! | 76 canceller_ = g_cancellable_new(); |
63 done_early = true; | 77 |
64 skipped_copy_ = true; | 78 g_input_stream_read_async(src_stream_, |
65 if (HasOutputPipe()) | 79 &buffer_[0], |
66 SetOutputObject(install_plan_); | 80 buffer_.size(), |
67 } | 81 G_PRIORITY_DEFAULT, |
68 if (!Unmount(dest_path_)) { | 82 canceller_, |
69 LOG(ERROR) << "Unmount failed. Aborting."; | 83 &FilesystemCopierAction::StaticAsyncReadyCallback, |
70 processor_->ActionComplete(this, false); | 84 this); |
71 return; | 85 read_in_flight_ = true; |
72 } | 86 |
73 if (done_early) { | 87 abort_action_completer.set_should_complete(false); |
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 } | 88 } |
86 | 89 |
87 void FilesystemCopierAction::TerminateProcessing() { | 90 void FilesystemCopierAction::TerminateProcessing() { |
88 if (is_mounted_) { | 91 if (canceller_) { |
89 LOG(ERROR) << "Aborted processing, but left a filesystem mounted."; | 92 g_cancellable_cancel(canceller_); |
90 } | 93 } |
91 } | 94 } |
92 | 95 |
93 bool FilesystemCopierAction::Mount(const string& device, | 96 void FilesystemCopierAction::Cleanup(bool success, bool was_cancelled) { |
94 const string& mountpoint) { | 97 g_object_unref(src_stream_); |
95 CHECK(!is_mounted_); | 98 src_stream_ = NULL; |
96 if(utils::MountFilesystem(device, mountpoint, 0)) | 99 g_object_unref(dst_stream_); |
97 is_mounted_ = true; | 100 dst_stream_ = NULL; |
98 return is_mounted_; | 101 if (was_cancelled) |
99 } | 102 return; |
100 | 103 if (success && HasOutputPipe()) |
101 bool FilesystemCopierAction::Unmount(const string& mountpoint) { | |
102 CHECK(is_mounted_); | |
103 if (utils::UnmountFilesystem(mountpoint)) | |
104 is_mounted_ = false; | |
105 return !is_mounted_; | |
106 } | |
107 | |
108 void* 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_); | 104 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 | |
151 void 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); | 105 processor_->ActionComplete(this, success); |
160 } | 106 } |
161 | 107 |
162 bool FilesystemCopierAction::CreateDirSynchronously(const std::string& new_path, | 108 void FilesystemCopierAction::AsyncReadyCallback(GObject *source_object, |
163 const struct stat& stbuf) { | 109 GAsyncResult *res) { |
164 int r = mkdir(new_path.c_str(), stbuf.st_mode); | 110 GError* error = NULL; |
165 TEST_AND_RETURN_FALSE_ERRNO(r == 0); | 111 CHECK(canceller_); |
166 return true; | 112 bool was_cancelled = g_cancellable_is_cancelled(canceller_) == TRUE; |
| 113 g_object_unref(canceller_); |
| 114 canceller_ = NULL; |
| 115 |
| 116 if (read_in_flight_) { |
| 117 ssize_t bytes_read = g_input_stream_read_finish(src_stream_, res, &error); |
| 118 if (bytes_read < 0) { |
| 119 LOG(ERROR) << "Read failed:" << utils::GetGErrorMessage(error); |
| 120 Cleanup(false, was_cancelled); |
| 121 return; |
| 122 } |
| 123 |
| 124 if (bytes_read == 0) { |
| 125 // We're done! |
| 126 Cleanup(true, was_cancelled); |
| 127 return; |
| 128 } |
| 129 // Kick off a write |
| 130 read_in_flight_ = false; |
| 131 buffer_valid_size_ = bytes_read; |
| 132 canceller_ = g_cancellable_new(); |
| 133 g_output_stream_write_async( |
| 134 dst_stream_, |
| 135 &buffer_[0], |
| 136 bytes_read, |
| 137 G_PRIORITY_DEFAULT, |
| 138 canceller_, |
| 139 &FilesystemCopierAction::StaticAsyncReadyCallback, |
| 140 this); |
| 141 return; |
| 142 } |
| 143 |
| 144 ssize_t bytes_written = g_output_stream_write_finish(dst_stream_, |
| 145 res, |
| 146 &error); |
| 147 if (bytes_written < static_cast<ssize_t>(buffer_valid_size_)) { |
| 148 if (bytes_written < 0) { |
| 149 LOG(ERROR) << "Write failed:" << utils::GetGErrorMessage(error); |
| 150 } else { |
| 151 LOG(ERROR) << "Write was short: wrote " << bytes_written |
| 152 << " but expected to write " << buffer_valid_size_; |
| 153 } |
| 154 Cleanup(false, was_cancelled); |
| 155 return; |
| 156 } |
| 157 |
| 158 // Kick off a read |
| 159 read_in_flight_ = true; |
| 160 canceller_ = g_cancellable_new(); |
| 161 g_input_stream_read_async( |
| 162 src_stream_, |
| 163 &buffer_[0], |
| 164 buffer_.size(), |
| 165 G_PRIORITY_DEFAULT, |
| 166 canceller_, |
| 167 &FilesystemCopierAction::StaticAsyncReadyCallback, |
| 168 this); |
167 } | 169 } |
168 | 170 |
169 bool 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 | |
207 bool 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 | |
215 bool 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 | |
230 bool 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 | |
239 bool 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 | |
304 const char* FilesystemCopierAction::kCompleteFilesystemMarker( | |
305 "/update_engine_copy_success"); | |
306 | |
307 } // namespace chromeos_update_engine | 171 } // namespace chromeos_update_engine |
OLD | NEW |