xref: /haiku/src/apps/installer/CopyEngine.cpp (revision 323b65468e5836bb27a5e373b14027d902349437)
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(&copy);
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