1 /* 2 * Copyright 2008-2009, Stephan Aßmus <superstippi@gmx.de> 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 6 #include "CopyEngine.h" 7 8 #include <new> 9 10 #include <math.h> 11 #include <stdio.h> 12 #include <string.h> 13 #include <sys/resource.h> 14 15 #include <Directory.h> 16 #include <fs_attr.h> 17 #include <NodeInfo.h> 18 #include <Path.h> 19 #include <String.h> 20 #include <SymLink.h> 21 22 #include "InstallerWindow.h" 23 // TODO: For PACKAGES_DIRECTORY and VAR_DIRECTORY, not so nice... 24 #include "SemaphoreLocker.h" 25 #include "ProgressReporter.h" 26 27 28 using std::nothrow; 29 30 31 CopyEngine::CopyEngine(ProgressReporter* reporter) 32 : 33 fBufferQueue(), 34 fWriterThread(-1), 35 fQuitting(false), 36 37 fBytesRead(0), 38 fLastBytesRead(0), 39 fItemsCopied(0), 40 fLastItemsCopied(0), 41 fTimeRead(0), 42 43 fBytesWritten(0), 44 fTimeWritten(0), 45 46 fBytesToCopy(0), 47 fItemsToCopy(0), 48 49 fCurrentTargetFolder(NULL), 50 fCurrentItem(NULL), 51 52 fProgressReporter(reporter) 53 { 54 fWriterThread = spawn_thread(_WriteThreadEntry, "buffer writer", 55 B_NORMAL_PRIORITY, this); 56 57 if (fWriterThread >= B_OK) 58 resume_thread(fWriterThread); 59 60 // ask for a bunch more file descriptors so that nested copying works well 61 struct rlimit rl; 62 rl.rlim_cur = 512; 63 rl.rlim_max = RLIM_SAVED_MAX; 64 setrlimit(RLIMIT_NOFILE, &rl); 65 } 66 67 68 CopyEngine::~CopyEngine() 69 { 70 while (fBufferQueue.Size() > 0) 71 snooze(10000); 72 73 fQuitting = true; 74 if (fWriterThread >= B_OK) { 75 int32 exitValue; 76 wait_for_thread(fWriterThread, &exitValue); 77 } 78 } 79 80 81 void 82 CopyEngine::ResetTargets(const char* source) 83 { 84 // TODO: One could subtract the bytes/items which were added to the 85 // ProgressReporter before resetting them... 86 87 fBytesRead = 0; 88 fLastBytesRead = 0; 89 fItemsCopied = 0; 90 fLastItemsCopied = 0; 91 fTimeRead = 0; 92 93 fBytesWritten = 0; 94 fTimeWritten = 0; 95 96 fBytesToCopy = 0; 97 fItemsToCopy = 0; 98 99 fCurrentTargetFolder = NULL; 100 fCurrentItem = NULL; 101 102 // init BEntry pointing to /var 103 // There is no other way to retrieve the path to the var folder 104 // on the source volume. Using find_directory() with 105 // B_COMMON_VAR_DIRECTORY will only ever get the var folder on the 106 // current /boot volume regardless of the volume of "source", which 107 // makes sense, since passing a volume is meant to folders that are 108 // volume specific, like "trash". 109 BPath path(source); 110 if (path.Append("common/var/swap") == B_OK) 111 fSwapFileEntry.SetTo(path.Path()); 112 else 113 fSwapFileEntry.Unset(); 114 } 115 116 117 status_t 118 CopyEngine::CollectTargets(const char* source, sem_id cancelSemaphore) 119 { 120 int32 level = 0; 121 status_t ret = _CollectCopyInfo(source, level, cancelSemaphore); 122 if (ret == B_OK && fProgressReporter != NULL) 123 fProgressReporter->AddItems(fItemsToCopy, fBytesToCopy); 124 return ret; 125 } 126 127 128 status_t 129 CopyEngine::CopyFolder(const char* source, const char* destination, 130 sem_id cancelSemaphore) 131 { 132 int32 level = 0; 133 return _CopyFolder(source, destination, level, cancelSemaphore); 134 } 135 136 137 status_t 138 CopyEngine::CopyFile(const BEntry& _source, const BEntry& _destination, 139 sem_id cancelSemaphore) 140 { 141 SemaphoreLocker lock(cancelSemaphore); 142 if (cancelSemaphore >= 0 && !lock.IsLocked()) { 143 // We are supposed to quit 144 printf("CopyFile - cancled\n"); 145 return B_CANCELED; 146 } 147 148 BFile source(&_source, B_READ_ONLY); 149 status_t ret = source.InitCheck(); 150 if (ret < B_OK) 151 return ret; 152 153 BFile* destination = new (nothrow) BFile(&_destination, 154 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 155 ret = destination->InitCheck(); 156 if (ret < B_OK) { 157 delete destination; 158 return ret; 159 } 160 161 int32 loopIteration = 0; 162 163 while (true) { 164 if (fBufferQueue.Size() >= BUFFER_COUNT) { 165 // the queue is "full", just wait a bit, the 166 // write thread will empty it 167 snooze(1000); 168 continue; 169 } 170 171 // allocate buffer 172 Buffer* buffer = new (nothrow) Buffer(destination); 173 if (!buffer || !buffer->buffer) { 174 delete destination; 175 delete buffer; 176 fprintf(stderr, "reading loop: out of memory\n"); 177 return B_NO_MEMORY; 178 } 179 180 // fill buffer 181 ssize_t read = source.Read(buffer->buffer, buffer->size); 182 if (read < 0) { 183 ret = (status_t)read; 184 fprintf(stderr, "Failed to read data: %s\n", strerror(ret)); 185 break; 186 } 187 188 fBytesRead += read; 189 loopIteration += 1; 190 if (loopIteration % 2 == 0) 191 _UpdateProgress(); 192 193 buffer->deleteFile = read == 0; 194 if (read > 0) 195 buffer->validBytes = (size_t)read; 196 else 197 buffer->validBytes = 0; 198 199 // enqueue the buffer 200 ret = fBufferQueue.Push(buffer); 201 if (ret < B_OK) { 202 buffer->deleteFile = false; 203 delete buffer; 204 delete destination; 205 return ret; 206 } 207 208 // quit if done 209 if (read == 0) 210 break; 211 } 212 213 return ret; 214 } 215 216 217 // #pragma mark - 218 219 220 status_t 221 CopyEngine::_CollectCopyInfo(const char* _source, int32& level, 222 sem_id cancelSemaphore) 223 { 224 level++; 225 226 BDirectory source(_source); 227 status_t ret = source.InitCheck(); 228 if (ret < B_OK) 229 return ret; 230 231 BEntry entry; 232 while (source.GetNextEntry(&entry) == B_OK) { 233 SemaphoreLocker lock(cancelSemaphore); 234 if (cancelSemaphore >= 0 && !lock.IsLocked()) { 235 // We are supposed to quit 236 return B_CANCELED; 237 } 238 239 struct stat statInfo; 240 entry.GetStat(&statInfo); 241 242 char name[B_FILE_NAME_LENGTH]; 243 status_t ret = entry.GetName(name); 244 if (ret < B_OK) 245 return ret; 246 247 if (!_ShouldCopyEntry(entry, name, statInfo, level)) 248 continue; 249 250 if (S_ISDIR(statInfo.st_mode)) { 251 // handle recursive directory copy 252 BPath srcFolder; 253 ret = entry.GetPath(&srcFolder); 254 if (ret < B_OK) 255 return ret; 256 257 if (cancelSemaphore >= 0) 258 lock.Unlock(); 259 260 ret = _CollectCopyInfo(srcFolder.Path(), level, cancelSemaphore); 261 if (ret < B_OK) 262 return ret; 263 } else if (S_ISLNK(statInfo.st_mode)) { 264 // link, ignore size 265 } else { 266 // file data 267 fBytesToCopy += statInfo.st_size; 268 } 269 270 fItemsToCopy++; 271 } 272 273 level--; 274 return B_OK; 275 } 276 277 278 status_t 279 CopyEngine::_CopyFolder(const char* _source, const char* _destination, 280 int32& level, sem_id cancelSemaphore) 281 { 282 level++; 283 fCurrentTargetFolder = _destination; 284 285 BDirectory source(_source); 286 status_t ret = source.InitCheck(); 287 if (ret < B_OK) 288 return ret; 289 290 ret = create_directory(_destination, 0777); 291 if (ret < B_OK && ret != B_FILE_EXISTS) { 292 fprintf(stderr, "Could not create '%s': %s\n", _destination, 293 strerror(ret)); 294 return ret; 295 } 296 297 BDirectory destination(_destination); 298 ret = destination.InitCheck(); 299 if (ret < B_OK) 300 return ret; 301 302 BEntry entry; 303 while (source.GetNextEntry(&entry) == B_OK) { 304 SemaphoreLocker lock(cancelSemaphore); 305 if (cancelSemaphore >= 0 && !lock.IsLocked()) { 306 // We are supposed to quit 307 return B_CANCELED; 308 } 309 310 char name[B_FILE_NAME_LENGTH]; 311 status_t ret = entry.GetName(name); 312 if (ret < B_OK) 313 return ret; 314 315 struct stat statInfo; 316 entry.GetStat(&statInfo); 317 318 if (!_ShouldCopyEntry(entry, name, statInfo, level)) 319 continue; 320 321 fItemsCopied++; 322 fCurrentItem = name; 323 _UpdateProgress(); 324 325 BEntry copy(&destination, name); 326 bool copyAttributes = true; 327 328 if (S_ISDIR(statInfo.st_mode)) { 329 // handle recursive directory copy 330 331 if (copy.Exists()) { 332 ret = B_OK; 333 if (copy.IsDirectory()) { 334 if (_ShouldClobberFolder(name, statInfo, level)) 335 ret = _RemoveFolder(copy); 336 else { 337 // Do not overwrite attributes on folders that exist. 338 // This should work better when the install target 339 // already contains a Haiku installation. 340 copyAttributes = false; 341 } 342 } else 343 ret = copy.Remove(); 344 345 if (ret != B_OK) { 346 fprintf(stderr, "Failed to make room for folder '%s': " 347 "%s\n", name, strerror(ret)); 348 return ret; 349 } 350 } 351 352 BPath srcFolder; 353 ret = entry.GetPath(&srcFolder); 354 if (ret < B_OK) 355 return ret; 356 357 BPath dstFolder; 358 ret = copy.GetPath(&dstFolder); 359 if (ret < B_OK) 360 return ret; 361 362 if (cancelSemaphore >= 0) 363 lock.Unlock(); 364 365 ret = _CopyFolder(srcFolder.Path(), dstFolder.Path(), level, 366 cancelSemaphore); 367 if (ret < B_OK) 368 return ret; 369 370 if (cancelSemaphore >= 0 && !lock.Lock()) { 371 // We are supposed to quit 372 return B_CANCELED; 373 } 374 } else { 375 if (copy.Exists()) { 376 if (copy.IsDirectory()) 377 ret = _RemoveFolder(copy); 378 else 379 ret = copy.Remove(); 380 if (ret != B_OK) { 381 fprintf(stderr, "Failed to make room for entry '%s': " 382 "%s\n", name, strerror(ret)); 383 return ret; 384 } 385 } 386 if (S_ISLNK(statInfo.st_mode)) { 387 // copy symbolic links 388 BSymLink srcLink(&entry); 389 if (ret < B_OK) 390 return ret; 391 392 char linkPath[B_PATH_NAME_LENGTH]; 393 ssize_t read = srcLink.ReadLink(linkPath, B_PATH_NAME_LENGTH - 1); 394 if (read < 0) 395 return (status_t)read; 396 397 // just in case it already exists... 398 copy.Remove(); 399 400 BSymLink dstLink; 401 ret = destination.CreateSymLink(name, linkPath, &dstLink); 402 if (ret < B_OK) 403 return ret; 404 } else { 405 // copy file data 406 // NOTE: Do not pass the locker, we simply keep holding the lock! 407 ret = CopyFile(entry, copy); 408 if (ret < B_OK) 409 return ret; 410 } 411 } 412 413 if (!copyAttributes) 414 continue; 415 416 ret = copy.SetTo(&destination, name); 417 if (ret != B_OK) 418 return ret; 419 420 // copy attributes 421 BNode sourceNode(&entry); 422 BNode targetNode(©); 423 char attrName[B_ATTR_NAME_LENGTH]; 424 while (sourceNode.GetNextAttrName(attrName) == B_OK) { 425 attr_info info; 426 if (sourceNode.GetAttrInfo(attrName, &info) < B_OK) 427 continue; 428 size_t size = 4096; 429 uint8 buffer[size]; 430 off_t offset = 0; 431 ssize_t read = sourceNode.ReadAttr(attrName, info.type, 432 offset, buffer, min_c(size, info.size)); 433 // NOTE: It's important to still write the attribute even if 434 // we have read 0 bytes! 435 while (read >= 0) { 436 targetNode.WriteAttr(attrName, info.type, offset, buffer, read); 437 offset += read; 438 read = sourceNode.ReadAttr(attrName, info.type, 439 offset, buffer, min_c(size, info.size - offset)); 440 if (read == 0) 441 break; 442 } 443 } 444 445 // copy basic attributes 446 copy.SetPermissions(statInfo.st_mode); 447 copy.SetOwner(statInfo.st_uid); 448 copy.SetGroup(statInfo.st_gid); 449 copy.SetModificationTime(statInfo.st_mtime); 450 copy.SetCreationTime(statInfo.st_crtime); 451 } 452 453 level--; 454 return B_OK; 455 } 456 457 458 status_t 459 CopyEngine::_RemoveFolder(BEntry& entry) 460 { 461 BDirectory directory(&entry); 462 status_t ret = directory.InitCheck(); 463 if (ret != B_OK) 464 return ret; 465 466 BEntry subEntry; 467 while (directory.GetNextEntry(&subEntry) == B_OK) { 468 if (subEntry.IsDirectory()) { 469 ret = _RemoveFolder(subEntry); 470 if (ret != B_OK) 471 return ret; 472 } else { 473 ret = subEntry.Remove(); 474 if (ret != B_OK) 475 return ret; 476 } 477 } 478 return entry.Remove(); 479 } 480 481 482 void 483 CopyEngine::_UpdateProgress() 484 { 485 if (fProgressReporter == NULL) 486 return; 487 488 uint64 items = 0; 489 if (fLastItemsCopied < fItemsCopied) { 490 items = fItemsCopied - fLastItemsCopied; 491 fLastItemsCopied = fItemsCopied; 492 } 493 494 off_t bytes = 0; 495 if (fLastBytesRead < fBytesRead) { 496 bytes = fBytesRead - fLastBytesRead; 497 fLastBytesRead = fBytesRead; 498 } 499 500 fProgressReporter->ItemsWritten(items, bytes, fCurrentItem, 501 fCurrentTargetFolder); 502 } 503 504 505 bool 506 CopyEngine::_ShouldCopyEntry(const BEntry& entry, const char* name, 507 const struct stat& statInfo, int32 level) const 508 { 509 if (level == 1 && S_ISDIR(statInfo.st_mode)) { 510 if (strcmp(VAR_DIRECTORY, name) == 0) { 511 // old location of /boot/var 512 printf("ignoring '%s'.\n", name); 513 return false; 514 } 515 if (strcmp(PACKAGES_DIRECTORY, name) == 0) { 516 printf("ignoring '%s'.\n", name); 517 return false; 518 } 519 if (strcmp(SOURCES_DIRECTORY, name) == 0) { 520 printf("ignoring '%s'.\n", name); 521 return false; 522 } 523 if (strcmp("rr_moved", name) == 0) { 524 printf("ignoring '%s'.\n", name); 525 return false; 526 } 527 } 528 if (level == 1 && S_ISREG(statInfo.st_mode)) { 529 if (strcmp("boot.catalog", name) == 0) { 530 printf("ignoring '%s'.\n", name); 531 return false; 532 } 533 if (strcmp("haiku-boot-floppy.image", name) == 0) { 534 printf("ignoring '%s'.\n", name); 535 return false; 536 } 537 } 538 if (fSwapFileEntry == entry) { 539 // current location of var 540 printf("ignoring swap file\n"); 541 return false; 542 } 543 return true; 544 } 545 546 547 bool 548 CopyEngine::_ShouldClobberFolder(const char* name, const struct stat& statInfo, 549 int32 level) const 550 { 551 if (level == 1 && S_ISDIR(statInfo.st_mode)) { 552 if (strcmp("system", name) == 0) { 553 printf("clobbering '%s'.\n", name); 554 return true; 555 } 556 // if (strcmp("develop", name) == 0) { 557 // printf("clobbering '%s'.\n", name); 558 // return true; 559 // } 560 } 561 return false; 562 } 563 564 565 int32 566 CopyEngine::_WriteThreadEntry(void* cookie) 567 { 568 CopyEngine* engine = (CopyEngine*)cookie; 569 engine->_WriteThread(); 570 return B_OK; 571 } 572 573 574 void 575 CopyEngine::_WriteThread() 576 { 577 bigtime_t bufferWaitTimeout = 100000; 578 579 while (!fQuitting) { 580 581 bigtime_t now = system_time(); 582 583 Buffer* buffer; 584 status_t ret = fBufferQueue.Pop(&buffer, bufferWaitTimeout); 585 if (ret == B_TIMED_OUT) { 586 // no buffer, timeout 587 continue; 588 } else if (ret == B_NO_INIT) { 589 // real error 590 return; 591 } else if (ret != B_OK) { 592 // no buffer, queue error 593 snooze(10000); 594 continue; 595 } 596 597 if (!buffer->deleteFile) { 598 ssize_t written = buffer->file->Write(buffer->buffer, 599 buffer->validBytes); 600 if (written != (ssize_t)buffer->validBytes) { 601 // TODO: this should somehow be propagated back 602 // to the main thread! 603 fprintf(stderr, "Failed to write data: %s\n", 604 strerror((status_t)written)); 605 } 606 fBytesWritten += written; 607 } 608 609 delete buffer; 610 611 // measure performance 612 fTimeWritten += system_time() - now; 613 } 614 615 double megaBytes = (double)fBytesWritten / (1024 * 1024); 616 double seconds = (fTimeWritten / 1000000); 617 if (seconds > 0) { 618 printf("%.2f MB written (%.2f MB/s)\n", megaBytes, 619 megaBytes / seconds); 620 } 621 } 622 623 624