| 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 |