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 28using std::nothrow; 29 30 31CopyEngine::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 68CopyEngine::~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 81void 82CopyEngine::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 117status_t 118CopyEngine::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 128status_t 129CopyEngine::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 137status_t 138CopyEngine::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 144printf("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 222status_t 223CopyEngine::_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 280status_t 281CopyEngine::_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 460status_t 461CopyEngine::_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 484void 485CopyEngine::_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 507bool 508CopyEngine::_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 549bool 550CopyEngine::_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 567int32 568CopyEngine::_WriteThreadEntry(void* cookie) 569{ 570 CopyEngine* engine = (CopyEngine*)cookie; 571 engine->_WriteThread(); 572 return B_OK; 573} 574 575 576void 577CopyEngine::_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