xref: /haiku/src/apps/installer/CopyEngine.cpp (revision 62f5ba006a08b0df30631375878effaf67ae5dbc)
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 	// init BEntry pointing to /var
108 	{
109 		// There is no other way to retrieve the path to the var folder
110 		// on the source volume. Using find_directory() with
111 		// B_COMMON_VAR_DIRECTORY will only ever get the var folder on the
112 		// current /boot volume regardless of the volume of "source", which
113 		// makes sense, since passing a volume is meant to folders that are
114 		// volume specific, like "trash".
115 		BPath path(source);
116 		if (path.Append("common/var") == B_OK)
117 			fVarDirectory.SetTo(path.Path());
118 		else
119 			fVarDirectory.Unset();
120 	}
121 
122 	int32 level = 0;
123 	status_t ret = _CollectCopyInfo(source, level, cancelSemaphore);
124 	if (ret == B_OK && fProgressReporter != NULL)
125 		fProgressReporter->AddItems(fItemsToCopy, fBytesToCopy);
126 	return ret;
127 }
128 
129 
130 status_t
131 CopyEngine::CopyFolder(const char* source, const char* destination,
132 	sem_id cancelSemaphore)
133 {
134 	int32 level = 0;
135 	return _CopyFolder(source, destination, level, cancelSemaphore);
136 }
137 
138 
139 status_t
140 CopyEngine::CopyFile(const BEntry& _source, const BEntry& _destination,
141 	sem_id cancelSemaphore)
142 {
143 	SemaphoreLocker lock(cancelSemaphore);
144 	if (cancelSemaphore >= 0 && !lock.IsLocked()) {
145 		// We are supposed to quit
146 printf("CopyFile - cancled\n");
147 		return B_CANCELED;
148 	}
149 
150 	BFile source(&_source, B_READ_ONLY);
151 	status_t ret = source.InitCheck();
152 	if (ret < B_OK)
153 		return ret;
154 
155 	BFile* destination = new (nothrow) BFile(&_destination,
156 		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
157 	ret = destination->InitCheck();
158 	if (ret < B_OK) {
159 		delete destination;
160 		return ret;
161 	}
162 
163 	int32 loopIteration = 0;
164 
165 	while (true) {
166 		if (fBufferQueue.Size() >= BUFFER_COUNT) {
167 			// the queue is "full", just wait a bit, the
168 			// write thread will empty it
169 			snooze(1000);
170 			continue;
171 		}
172 
173 		// allocate buffer
174 		Buffer* buffer = new (nothrow) Buffer(destination);
175 		if (!buffer || !buffer->buffer) {
176 			delete destination;
177 			delete buffer;
178 			fprintf(stderr, "reading loop: out of memory\n");
179 			return B_NO_MEMORY;
180 		}
181 
182 		// fill buffer
183 		ssize_t read = source.Read(buffer->buffer, buffer->size);
184 		if (read < 0) {
185 			ret = (status_t)read;
186 			fprintf(stderr, "Failed to read data: %s\n", strerror(ret));
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("rr_moved", name) == 0) {
522 			printf("ignoring '%s'.\n", name);
523 			return false;
524 		}
525 	}
526 	if (level == 1 && S_ISREG(statInfo.st_mode)) {
527 		if (strcmp("boot.catalog", name) == 0) {
528 			printf("ignoring '%s'.\n", name);
529 			return false;
530 		}
531 		if (strcmp("haiku-boot-floppy.image", name) == 0) {
532 			printf("ignoring '%s'.\n", name);
533 			return false;
534 		}
535 	}
536 	if (fVarDirectory == entry) {
537 		// current location of var
538 		printf("ignoring '%s'.\n", name);
539 		return false;
540 	}
541 	return true;
542 }
543 
544 
545 bool
546 CopyEngine::_ShouldClobberFolder(const char* name, const struct stat& statInfo,
547 	int32 level) const
548 {
549 	if (level == 1 && S_ISDIR(statInfo.st_mode)) {
550 		if (strcmp("system", name) == 0) {
551 			printf("clobbering '%s'.\n", name);
552 			return true;
553 		}
554 //		if (strcmp("develop", name) == 0) {
555 //			printf("clobbering '%s'.\n", name);
556 //			return true;
557 //		}
558 	}
559 	return false;
560 }
561 
562 
563 int32
564 CopyEngine::_WriteThreadEntry(void* cookie)
565 {
566 	CopyEngine* engine = (CopyEngine*)cookie;
567 	engine->_WriteThread();
568 	return B_OK;
569 }
570 
571 
572 void
573 CopyEngine::_WriteThread()
574 {
575 	bigtime_t bufferWaitTimeout = 100000;
576 
577 	while (!fQuitting) {
578 
579 		bigtime_t now = system_time();
580 
581 		Buffer* buffer;
582 		status_t ret = fBufferQueue.Pop(&buffer, bufferWaitTimeout);
583 		if (ret == B_TIMED_OUT) {
584 			// no buffer, timeout
585 			continue;
586 		} else if (ret == B_NO_INIT) {
587 			// real error
588 			return;
589 		} else if (ret != B_OK) {
590 			// no buffer, queue error
591 			snooze(10000);
592 			continue;
593 		}
594 
595 		if (!buffer->deleteFile) {
596 			ssize_t written = buffer->file->Write(buffer->buffer,
597 				buffer->validBytes);
598 			if (written != (ssize_t)buffer->validBytes) {
599 				// TODO: this should somehow be propagated back
600 				// to the main thread!
601 				fprintf(stderr, "Failed to write data: %s\n",
602 					strerror((status_t)written));
603 			}
604 			fBytesWritten += written;
605 		}
606 
607 		delete buffer;
608 
609 		// measure performance
610 		fTimeWritten += system_time() - now;
611 	}
612 
613 	double megaBytes = (double)fBytesWritten / (1024 * 1024);
614 	double seconds = (fTimeWritten / 1000000);
615 	if (seconds > 0) {
616 		printf("%.2f MB written (%.2f MB/s)\n", megaBytes,
617 			megaBytes / seconds);
618 	}
619 }
620 
621 
622