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