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