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