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 delete buffer; 186 delete destination; 187 break; 188 } 189 190 fBytesRead += read; 191 loopIteration += 1; 192 if (loopIteration % 2 == 0) 193 _UpdateProgress(); 194 195 buffer->deleteFile = read == 0; 196 if (read > 0) 197 buffer->validBytes = (size_t)read; 198 else 199 buffer->validBytes = 0; 200 201 // enqueue the buffer 202 ret = fBufferQueue.Push(buffer); 203 if (ret < B_OK) { 204 buffer->deleteFile = false; 205 delete buffer; 206 delete destination; 207 return ret; 208 } 209 210 // quit if done 211 if (read == 0) 212 break; 213 } 214 215 return ret; 216 } 217 218 219 // #pragma mark - 220 221 222 status_t 223 CopyEngine::_CollectCopyInfo(const char* _source, int32& level, 224 sem_id cancelSemaphore) 225 { 226 level++; 227 228 BDirectory source(_source); 229 status_t ret = source.InitCheck(); 230 if (ret < B_OK) 231 return ret; 232 233 BEntry entry; 234 while (source.GetNextEntry(&entry) == B_OK) { 235 SemaphoreLocker lock(cancelSemaphore); 236 if (cancelSemaphore >= 0 && !lock.IsLocked()) { 237 // We are supposed to quit 238 return B_CANCELED; 239 } 240 241 struct stat statInfo; 242 entry.GetStat(&statInfo); 243 244 char name[B_FILE_NAME_LENGTH]; 245 status_t ret = entry.GetName(name); 246 if (ret < B_OK) 247 return ret; 248 249 if (!_ShouldCopyEntry(entry, name, statInfo, level)) 250 continue; 251 252 if (S_ISDIR(statInfo.st_mode)) { 253 // handle recursive directory copy 254 BPath srcFolder; 255 ret = entry.GetPath(&srcFolder); 256 if (ret < B_OK) 257 return ret; 258 259 if (cancelSemaphore >= 0) 260 lock.Unlock(); 261 262 ret = _CollectCopyInfo(srcFolder.Path(), level, cancelSemaphore); 263 if (ret < B_OK) 264 return ret; 265 } else if (S_ISLNK(statInfo.st_mode)) { 266 // link, ignore size 267 } else { 268 // file data 269 fBytesToCopy += statInfo.st_size; 270 } 271 272 fItemsToCopy++; 273 } 274 275 level--; 276 return B_OK; 277 } 278 279 280 status_t 281 CopyEngine::_CopyFolder(const char* _source, const char* _destination, 282 int32& level, sem_id cancelSemaphore) 283 { 284 level++; 285 fCurrentTargetFolder = _destination; 286 287 BDirectory source(_source); 288 status_t ret = source.InitCheck(); 289 if (ret < B_OK) 290 return ret; 291 292 ret = create_directory(_destination, 0777); 293 if (ret < B_OK && ret != B_FILE_EXISTS) { 294 fprintf(stderr, "Could not create '%s': %s\n", _destination, 295 strerror(ret)); 296 return ret; 297 } 298 299 BDirectory destination(_destination); 300 ret = destination.InitCheck(); 301 if (ret < B_OK) 302 return ret; 303 304 BEntry entry; 305 while (source.GetNextEntry(&entry) == B_OK) { 306 SemaphoreLocker lock(cancelSemaphore); 307 if (cancelSemaphore >= 0 && !lock.IsLocked()) { 308 // We are supposed to quit 309 return B_CANCELED; 310 } 311 312 char name[B_FILE_NAME_LENGTH]; 313 status_t ret = entry.GetName(name); 314 if (ret < B_OK) 315 return ret; 316 317 struct stat statInfo; 318 entry.GetStat(&statInfo); 319 320 if (!_ShouldCopyEntry(entry, name, statInfo, level)) 321 continue; 322 323 fItemsCopied++; 324 fCurrentItem = name; 325 _UpdateProgress(); 326 327 BEntry copy(&destination, name); 328 bool copyAttributes = true; 329 330 if (S_ISDIR(statInfo.st_mode)) { 331 // handle recursive directory copy 332 333 if (copy.Exists()) { 334 ret = B_OK; 335 if (copy.IsDirectory()) { 336 if (_ShouldClobberFolder(name, statInfo, level)) 337 ret = _RemoveFolder(copy); 338 else { 339 // Do not overwrite attributes on folders that exist. 340 // This should work better when the install target 341 // already contains a Haiku installation. 342 copyAttributes = false; 343 } 344 } else 345 ret = copy.Remove(); 346 347 if (ret != B_OK) { 348 fprintf(stderr, "Failed to make room for folder '%s': " 349 "%s\n", name, strerror(ret)); 350 return ret; 351 } 352 } 353 354 BPath srcFolder; 355 ret = entry.GetPath(&srcFolder); 356 if (ret < B_OK) 357 return ret; 358 359 BPath dstFolder; 360 ret = copy.GetPath(&dstFolder); 361 if (ret < B_OK) 362 return ret; 363 364 if (cancelSemaphore >= 0) 365 lock.Unlock(); 366 367 ret = _CopyFolder(srcFolder.Path(), dstFolder.Path(), level, 368 cancelSemaphore); 369 if (ret < B_OK) 370 return ret; 371 372 if (cancelSemaphore >= 0 && !lock.Lock()) { 373 // We are supposed to quit 374 return B_CANCELED; 375 } 376 } else { 377 if (copy.Exists()) { 378 if (copy.IsDirectory()) 379 ret = _RemoveFolder(copy); 380 else 381 ret = copy.Remove(); 382 if (ret != B_OK) { 383 fprintf(stderr, "Failed to make room for entry '%s': " 384 "%s\n", name, strerror(ret)); 385 return ret; 386 } 387 } 388 if (S_ISLNK(statInfo.st_mode)) { 389 // copy symbolic links 390 BSymLink srcLink(&entry); 391 if (ret < B_OK) 392 return ret; 393 394 char linkPath[B_PATH_NAME_LENGTH]; 395 ssize_t read = srcLink.ReadLink(linkPath, B_PATH_NAME_LENGTH - 1); 396 if (read < 0) 397 return (status_t)read; 398 399 // just in case it already exists... 400 copy.Remove(); 401 402 BSymLink dstLink; 403 ret = destination.CreateSymLink(name, linkPath, &dstLink); 404 if (ret < B_OK) 405 return ret; 406 } else { 407 // copy file data 408 // NOTE: Do not pass the locker, we simply keep holding the lock! 409 ret = CopyFile(entry, copy); 410 if (ret < B_OK) 411 return ret; 412 } 413 } 414 415 if (!copyAttributes) 416 continue; 417 418 ret = copy.SetTo(&destination, name); 419 if (ret != B_OK) 420 return ret; 421 422 // copy attributes 423 BNode sourceNode(&entry); 424 BNode targetNode(©); 425 char attrName[B_ATTR_NAME_LENGTH]; 426 while (sourceNode.GetNextAttrName(attrName) == B_OK) { 427 attr_info info; 428 if (sourceNode.GetAttrInfo(attrName, &info) < B_OK) 429 continue; 430 size_t size = 4096; 431 uint8 buffer[size]; 432 off_t offset = 0; 433 ssize_t read = sourceNode.ReadAttr(attrName, info.type, 434 offset, buffer, min_c(size, info.size)); 435 // NOTE: It's important to still write the attribute even if 436 // we have read 0 bytes! 437 while (read >= 0) { 438 targetNode.WriteAttr(attrName, info.type, offset, buffer, read); 439 offset += read; 440 read = sourceNode.ReadAttr(attrName, info.type, 441 offset, buffer, min_c(size, info.size - offset)); 442 if (read == 0) 443 break; 444 } 445 } 446 447 // copy basic attributes 448 copy.SetPermissions(statInfo.st_mode); 449 copy.SetOwner(statInfo.st_uid); 450 copy.SetGroup(statInfo.st_gid); 451 copy.SetModificationTime(statInfo.st_mtime); 452 copy.SetCreationTime(statInfo.st_crtime); 453 } 454 455 level--; 456 return B_OK; 457 } 458 459 460 status_t 461 CopyEngine::_RemoveFolder(BEntry& entry) 462 { 463 BDirectory directory(&entry); 464 status_t ret = directory.InitCheck(); 465 if (ret != B_OK) 466 return ret; 467 468 BEntry subEntry; 469 while (directory.GetNextEntry(&subEntry) == B_OK) { 470 if (subEntry.IsDirectory()) { 471 ret = _RemoveFolder(subEntry); 472 if (ret != B_OK) 473 return ret; 474 } else { 475 ret = subEntry.Remove(); 476 if (ret != B_OK) 477 return ret; 478 } 479 } 480 return entry.Remove(); 481 } 482 483 484 void 485 CopyEngine::_UpdateProgress() 486 { 487 if (fProgressReporter == NULL) 488 return; 489 490 uint64 items = 0; 491 if (fLastItemsCopied < fItemsCopied) { 492 items = fItemsCopied - fLastItemsCopied; 493 fLastItemsCopied = fItemsCopied; 494 } 495 496 off_t bytes = 0; 497 if (fLastBytesRead < fBytesRead) { 498 bytes = fBytesRead - fLastBytesRead; 499 fLastBytesRead = fBytesRead; 500 } 501 502 fProgressReporter->ItemsWritten(items, bytes, fCurrentItem, 503 fCurrentTargetFolder); 504 } 505 506 507 bool 508 CopyEngine::_ShouldCopyEntry(const BEntry& entry, const char* name, 509 const struct stat& statInfo, int32 level) const 510 { 511 if (level == 1 && S_ISDIR(statInfo.st_mode)) { 512 if (strcmp(VAR_DIRECTORY, name) == 0) { 513 // old location of /boot/var 514 printf("ignoring '%s'.\n", name); 515 return false; 516 } 517 if (strcmp(PACKAGES_DIRECTORY, name) == 0) { 518 printf("ignoring '%s'.\n", name); 519 return false; 520 } 521 if (strcmp(SOURCES_DIRECTORY, name) == 0) { 522 printf("ignoring '%s'.\n", name); 523 return false; 524 } 525 if (strcmp("rr_moved", name) == 0) { 526 printf("ignoring '%s'.\n", name); 527 return false; 528 } 529 } 530 if (level == 1 && S_ISREG(statInfo.st_mode)) { 531 if (strcmp("boot.catalog", name) == 0) { 532 printf("ignoring '%s'.\n", name); 533 return false; 534 } 535 if (strcmp("haiku-boot-floppy.image", name) == 0) { 536 printf("ignoring '%s'.\n", name); 537 return false; 538 } 539 } 540 if (fSwapFileEntry == entry) { 541 // current location of var 542 printf("ignoring swap file\n"); 543 return false; 544 } 545 return true; 546 } 547 548 549 bool 550 CopyEngine::_ShouldClobberFolder(const char* name, const struct stat& statInfo, 551 int32 level) const 552 { 553 if (level == 1 && S_ISDIR(statInfo.st_mode)) { 554 if (strcmp("system", name) == 0) { 555 printf("clobbering '%s'.\n", name); 556 return true; 557 } 558 // if (strcmp("develop", name) == 0) { 559 // printf("clobbering '%s'.\n", name); 560 // return true; 561 // } 562 } 563 return false; 564 } 565 566 567 int32 568 CopyEngine::_WriteThreadEntry(void* cookie) 569 { 570 CopyEngine* engine = (CopyEngine*)cookie; 571 engine->_WriteThread(); 572 return B_OK; 573 } 574 575 576 void 577 CopyEngine::_WriteThread() 578 { 579 bigtime_t bufferWaitTimeout = 100000; 580 581 while (!fQuitting) { 582 583 bigtime_t now = system_time(); 584 585 Buffer* buffer; 586 status_t ret = fBufferQueue.Pop(&buffer, bufferWaitTimeout); 587 if (ret == B_TIMED_OUT) { 588 // no buffer, timeout 589 continue; 590 } else if (ret == B_NO_INIT) { 591 // real error 592 return; 593 } else if (ret != B_OK) { 594 // no buffer, queue error 595 snooze(10000); 596 continue; 597 } 598 599 if (!buffer->deleteFile) { 600 ssize_t written = buffer->file->Write(buffer->buffer, 601 buffer->validBytes); 602 if (written != (ssize_t)buffer->validBytes) { 603 // TODO: this should somehow be propagated back 604 // to the main thread! 605 fprintf(stderr, "Failed to write data: %s\n", 606 strerror((status_t)written)); 607 } 608 fBytesWritten += written; 609 } 610 611 delete buffer; 612 613 // measure performance 614 fTimeWritten += system_time() - now; 615 } 616 617 double megaBytes = (double)fBytesWritten / (1024 * 1024); 618 double seconds = (fTimeWritten / 1000000); 619 if (seconds > 0) { 620 printf("%.2f MB written (%.2f MB/s)\n", megaBytes, 621 megaBytes / seconds); 622 } 623 } 624 625 626