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