xref: /haiku/src/apps/installer/CopyEngine.cpp (revision dfb36b35b94318fa81319ce25a0b6288901b9b24)
1 /*
2  * Copyright 2008-2009, Stephan Aßmus <superstippi@gmx.de>
3  * Copyright 2014, Axel Dörfler, axeld@pinc-software.de
4  * All rights reserved. Distributed under the terms of the MIT License.
5  */
6 
7 
8 #include "CopyEngine.h"
9 
10 #include <new>
11 
12 #include <math.h>
13 #include <stdio.h>
14 #include <string.h>
15 #include <sys/resource.h>
16 
17 #include <Directory.h>
18 #include <fs_attr.h>
19 #include <NodeInfo.h>
20 #include <Path.h>
21 #include <SymLink.h>
22 
23 #include "SemaphoreLocker.h"
24 #include "ProgressReporter.h"
25 
26 
27 using std::nothrow;
28 
29 
30 // #pragma mark - EntryFilter
31 
32 
~EntryFilter()33 CopyEngine::EntryFilter::~EntryFilter()
34 {
35 }
36 
37 
38 // #pragma mark - CopyEngine
39 
40 
CopyEngine(ProgressReporter * reporter,EntryFilter * entryFilter)41 CopyEngine::CopyEngine(ProgressReporter* reporter, EntryFilter* entryFilter)
42 	:
43 	fBufferQueue(),
44 	fWriterThread(-1),
45 	fQuitting(false),
46 
47 	fAbsoluteSourcePath(),
48 
49 	fBytesRead(0),
50 	fLastBytesRead(0),
51 	fItemsCopied(0),
52 	fLastItemsCopied(0),
53 	fTimeRead(0),
54 
55 	fBytesWritten(0),
56 	fTimeWritten(0),
57 
58 	fCurrentTargetFolder(NULL),
59 	fCurrentItem(NULL),
60 
61 	fProgressReporter(reporter),
62 	fEntryFilter(entryFilter)
63 {
64 	fWriterThread = spawn_thread(_WriteThreadEntry, "buffer writer",
65 		B_NORMAL_PRIORITY, this);
66 
67 	if (fWriterThread >= B_OK)
68 		resume_thread(fWriterThread);
69 
70 	// ask for a bunch more file descriptors so that nested copying works well
71 	struct rlimit rl;
72 	rl.rlim_cur = 512;
73 	rl.rlim_max = RLIM_SAVED_MAX;
74 	setrlimit(RLIMIT_NOFILE, &rl);
75 }
76 
77 
~CopyEngine()78 CopyEngine::~CopyEngine()
79 {
80 	while (fBufferQueue.Size() > 0)
81 		snooze(10000);
82 
83 	fQuitting = true;
84 	if (fWriterThread >= B_OK) {
85 		int32 exitValue;
86 		wait_for_thread(fWriterThread, &exitValue);
87 	}
88 }
89 
90 
91 void
ResetTargets(const char * source)92 CopyEngine::ResetTargets(const char* source)
93 {
94 	// TODO: One could subtract the bytes/items which were added to the
95 	// ProgressReporter before resetting them...
96 
97 	fAbsoluteSourcePath = source;
98 
99 	fBytesRead = 0;
100 	fLastBytesRead = 0;
101 	fItemsCopied = 0;
102 	fLastItemsCopied = 0;
103 	fTimeRead = 0;
104 
105 	fBytesWritten = 0;
106 	fTimeWritten = 0;
107 
108 	fCurrentTargetFolder = NULL;
109 	fCurrentItem = NULL;
110 }
111 
112 
113 status_t
CollectTargets(const char * source,sem_id cancelSemaphore)114 CopyEngine::CollectTargets(const char* source, sem_id cancelSemaphore)
115 {
116 	off_t bytesToCopy = 0;
117 	uint64 itemsToCopy = 0;
118 	status_t ret = _CollectCopyInfo(source, cancelSemaphore, bytesToCopy,
119 			itemsToCopy);
120 	if (ret == B_OK && fProgressReporter != NULL)
121 		fProgressReporter->AddItems(itemsToCopy, bytesToCopy);
122 	return ret;
123 }
124 
125 
126 status_t
Copy(const char * _source,const char * _destination,sem_id cancelSemaphore,bool copyAttributes)127 CopyEngine::Copy(const char* _source, const char* _destination,
128 	sem_id cancelSemaphore, bool copyAttributes)
129 {
130 	status_t ret;
131 
132 	BEntry source(_source);
133 	ret = source.InitCheck();
134 	if (ret != B_OK)
135 		return ret;
136 
137 	BEntry destination(_destination);
138 	ret = destination.InitCheck();
139 	if (ret != B_OK)
140 		return ret;
141 
142 	return _Copy(source, destination, cancelSemaphore, copyAttributes);
143 }
144 
145 
146 status_t
RemoveFolder(BEntry & entry)147 CopyEngine::RemoveFolder(BEntry& entry)
148 {
149 	BDirectory directory(&entry);
150 	status_t ret = directory.InitCheck();
151 	if (ret != B_OK)
152 		return ret;
153 
154 	BEntry subEntry;
155 	while (directory.GetNextEntry(&subEntry) == B_OK) {
156 		if (subEntry.IsDirectory()) {
157 			ret = CopyEngine::RemoveFolder(subEntry);
158 			if (ret != B_OK)
159 				return ret;
160 		} else {
161 			ret = subEntry.Remove();
162 			if (ret != B_OK)
163 				return ret;
164 		}
165 	}
166 	return entry.Remove();
167 }
168 
169 
170 status_t
_CopyData(const BEntry & _source,const BEntry & _destination,sem_id cancelSemaphore)171 CopyEngine::_CopyData(const BEntry& _source, const BEntry& _destination,
172 	sem_id cancelSemaphore)
173 {
174 	SemaphoreLocker lock(cancelSemaphore);
175 	if (cancelSemaphore >= 0 && !lock.IsLocked()) {
176 		// We are supposed to quit
177 		return B_CANCELED;
178 	}
179 
180 	BFile source(&_source, B_READ_ONLY);
181 	status_t ret = source.InitCheck();
182 	if (ret < B_OK)
183 		return ret;
184 
185 	BFile* destination = new (nothrow) BFile(&_destination,
186 		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
187 	ret = destination->InitCheck();
188 	if (ret < B_OK) {
189 		delete destination;
190 		return ret;
191 	}
192 
193 	int32 loopIteration = 0;
194 
195 	while (true) {
196 		if (fBufferQueue.Size() >= BUFFER_COUNT) {
197 			// the queue is "full", just wait a bit, the
198 			// write thread will empty it
199 			snooze(1000);
200 			continue;
201 		}
202 
203 		// allocate buffer
204 		Buffer* buffer = new (nothrow) Buffer(destination);
205 		if (!buffer || !buffer->buffer) {
206 			delete destination;
207 			delete buffer;
208 			fprintf(stderr, "reading loop: out of memory\n");
209 			return B_NO_MEMORY;
210 		}
211 
212 		// fill buffer
213 		ssize_t read = source.Read(buffer->buffer, buffer->size);
214 		if (read < 0) {
215 			ret = (status_t)read;
216 			fprintf(stderr, "Failed to read data: %s\n", strerror(ret));
217 			delete buffer;
218 			delete destination;
219 			break;
220 		}
221 
222 		fBytesRead += read;
223 		loopIteration += 1;
224 		if (loopIteration % 2 == 0)
225 			_UpdateProgress();
226 
227 		buffer->deleteFile = read == 0;
228 		if (read > 0)
229 			buffer->validBytes = (size_t)read;
230 		else
231 			buffer->validBytes = 0;
232 
233 		// enqueue the buffer
234 		ret = fBufferQueue.Push(buffer);
235 		if (ret < B_OK) {
236 			buffer->deleteFile = false;
237 			delete buffer;
238 			delete destination;
239 			return ret;
240 		}
241 
242 		// quit if done
243 		if (read == 0)
244 			break;
245 	}
246 
247 	return ret;
248 }
249 
250 
251 // #pragma mark -
252 
253 
254 status_t
_CollectCopyInfo(const char * _source,sem_id cancelSemaphore,off_t & bytesToCopy,uint64 & itemsToCopy)255 CopyEngine::_CollectCopyInfo(const char* _source, sem_id cancelSemaphore,
256 	off_t& bytesToCopy, uint64& itemsToCopy)
257 {
258 	BEntry source(_source);
259 	status_t ret = source.InitCheck();
260 	if (ret < B_OK)
261 		return ret;
262 
263 	struct stat statInfo;
264 	ret = source.GetStat(&statInfo);
265 	if (ret < B_OK)
266 		return ret;
267 
268 	SemaphoreLocker lock(cancelSemaphore);
269 	if (cancelSemaphore >= 0 && !lock.IsLocked()) {
270 		// We are supposed to quit
271 		return B_CANCELED;
272 	}
273 
274 	if (fEntryFilter != NULL
275 		&& !fEntryFilter->ShouldCopyEntry(source,
276 			_RelativeEntryPath(_source), statInfo)) {
277 		// Skip this entry
278 		return B_OK;
279 	}
280 
281 	if (cancelSemaphore >= 0)
282 		lock.Unlock();
283 
284 	if (S_ISDIR(statInfo.st_mode)) {
285 		BDirectory srcFolder(&source);
286 		ret = srcFolder.InitCheck();
287 		if (ret < B_OK)
288 			return ret;
289 
290 		BEntry entry;
291 		while (srcFolder.GetNextEntry(&entry) == B_OK) {
292 			BPath entryPath;
293 			ret = entry.GetPath(&entryPath);
294 			if (ret < B_OK)
295 				return ret;
296 
297 			ret = _CollectCopyInfo(entryPath.Path(), cancelSemaphore,
298 					bytesToCopy, itemsToCopy);
299 			if (ret < B_OK)
300 				return ret;
301 		}
302 	} else if (S_ISLNK(statInfo.st_mode)) {
303 		// link, ignore size
304 	} else {
305 		bytesToCopy += statInfo.st_size;
306 	}
307 
308 	itemsToCopy++;
309 	return B_OK;
310 }
311 
312 
313 status_t
_Copy(BEntry & source,BEntry & destination,sem_id cancelSemaphore,bool copyAttributes)314 CopyEngine::_Copy(BEntry &source, BEntry &destination,
315 	sem_id cancelSemaphore, bool copyAttributes)
316 {
317 	struct stat sourceInfo;
318 	status_t ret = source.GetStat(&sourceInfo);
319 	if (ret != B_OK)
320 		return ret;
321 
322 	SemaphoreLocker lock(cancelSemaphore);
323 	if (cancelSemaphore >= 0 && !lock.IsLocked()) {
324 		// We are supposed to quit
325 		return B_CANCELED;
326 	}
327 
328 	BPath sourcePath(&source);
329 	ret = sourcePath.InitCheck();
330 	if (ret != B_OK)
331 		return ret;
332 
333 	BPath destPath(&destination);
334 	ret = destPath.InitCheck();
335 	if (ret != B_OK)
336 		return ret;
337 
338 	const char *relativeSourcePath = _RelativeEntryPath(sourcePath.Path());
339 	if (fEntryFilter != NULL
340 		&& !fEntryFilter->ShouldCopyEntry(source, relativeSourcePath,
341 			sourceInfo)) {
342 		// Silently skip the filtered entry.
343 		return B_OK;
344 	}
345 
346 	if (cancelSemaphore >= 0)
347 		lock.Unlock();
348 
349 	bool copyAttributesToTarget = copyAttributes;
350 		// attributes of the current source to the destination will be copied
351 		// when copyAttributes is set to true, but there may be exceptions, so
352 		// allow the recursively used copyAttribute parameter to be overridden
353 		// for the current target.
354 	if (S_ISDIR(sourceInfo.st_mode)) {
355 		BDirectory sourceDirectory(&source);
356 		ret = sourceDirectory.InitCheck();
357 		if (ret != B_OK)
358 			return ret;
359 
360 		if (destination.Exists()) {
361 			if (destination.IsDirectory()) {
362 				// Do not overwrite attributes on folders that exist.
363 				// This should work better when the install target
364 				// already contains a Haiku installation.
365 				copyAttributesToTarget = false;
366 			} else {
367 				ret = destination.Remove();
368 			}
369 
370 			if (ret != B_OK) {
371 				fprintf(stderr, "Failed to make room for folder '%s': "
372 					"%s\n", sourcePath.Path(), strerror(ret));
373 				return ret;
374 			}
375 		}
376 
377 		ret = create_directory(destPath.Path(), 0777);
378 			// Make sure the target path exists, it may have been deleted if
379 			// the existing destination was a file instead of a directory.
380 		if (ret != B_OK && ret != B_FILE_EXISTS) {
381 			fprintf(stderr, "Could not create '%s': %s\n", destPath.Path(),
382 				strerror(ret));
383 			return ret;
384 		}
385 
386 		BDirectory destDirectory(&destination);
387 		ret = destDirectory.InitCheck();
388 		if (ret != B_OK)
389 			return ret;
390 
391 		BEntry entry;
392 		while (sourceDirectory.GetNextEntry(&entry) == B_OK) {
393 			BEntry dest(&destDirectory, entry.Name());
394 			ret = dest.InitCheck();
395 			if (ret != B_OK)
396 				return ret;
397 			ret = _Copy(entry, dest, cancelSemaphore, copyAttributes);
398 			if (ret != B_OK)
399 				return ret;
400 		}
401 	} else {
402 		if (destination.Exists()) {
403 			if (destination.IsDirectory())
404 				ret = CopyEngine::RemoveFolder(destination);
405 			else
406 				ret = destination.Remove();
407 			if (ret != B_OK) {
408 				fprintf(stderr, "Failed to make room for entry '%s': "
409 					"%s\n", sourcePath.Path(), strerror(ret));
410 				return ret;
411 			}
412 		}
413 
414 		fItemsCopied++;
415 		BPath destDirectory;
416 		ret = destPath.GetParent(&destDirectory);
417 		if (ret != B_OK)
418 			return ret;
419 		fCurrentTargetFolder = destDirectory.Path();
420 		fCurrentItem = sourcePath.Leaf();
421 		_UpdateProgress();
422 
423 		if (S_ISLNK(sourceInfo.st_mode)) {
424 			// copy symbolic links
425 			BSymLink srcLink(&source);
426 			ret = srcLink.InitCheck();
427 			if (ret != B_OK)
428 				return ret;
429 
430 			char linkPath[B_PATH_NAME_LENGTH];
431 			ssize_t read = srcLink.ReadLink(linkPath, B_PATH_NAME_LENGTH - 1);
432 			if (read < 0)
433 				return (status_t)read;
434 
435 			BDirectory dstFolder;
436 			ret = destination.GetParent(&dstFolder);
437 			if (ret != B_OK)
438 				return ret;
439 			ret = dstFolder.CreateSymLink(sourcePath.Leaf(), linkPath, NULL);
440 			if (ret != B_OK)
441 				return ret;
442 		} else {
443 			// copy file data
444 			// NOTE: Do not pass the locker, we simply keep holding the lock!
445 			ret = _CopyData(source, destination);
446 			if (ret != B_OK)
447 				return ret;
448 		}
449 	}
450 
451 	if (copyAttributesToTarget) {
452 		// copy attributes to the current target
453 		BNode sourceNode(&source);
454 		BNode targetNode(&destination);
455 		char attrName[B_ATTR_NAME_LENGTH];
456 		while (sourceNode.GetNextAttrName(attrName) == B_OK) {
457 			attr_info info;
458 			if (sourceNode.GetAttrInfo(attrName, &info) != B_OK)
459 				continue;
460 			size_t size = 4096;
461 			uint8 buffer[size];
462 			off_t offset = 0;
463 			ssize_t read = sourceNode.ReadAttr(attrName, info.type,
464 				offset, buffer, std::min((off_t)size, info.size));
465 			// NOTE: It's important to still write the attribute even if
466 			// we have read 0 bytes!
467 			while (read >= 0) {
468 				targetNode.WriteAttr(attrName, info.type, offset, buffer, read);
469 				offset += read;
470 				read = sourceNode.ReadAttr(attrName, info.type,
471 					offset, buffer, std::min((off_t)size, info.size - offset));
472 				if (read == 0)
473 					break;
474 			}
475 		}
476 
477 		// copy basic attributes
478 		destination.SetPermissions(sourceInfo.st_mode);
479 		destination.SetOwner(sourceInfo.st_uid);
480 		destination.SetGroup(sourceInfo.st_gid);
481 		destination.SetModificationTime(sourceInfo.st_mtime);
482 		destination.SetCreationTime(sourceInfo.st_crtime);
483 	}
484 
485 	return B_OK;
486 }
487 
488 
489 const char*
_RelativeEntryPath(const char * absoluteSourcePath) const490 CopyEngine::_RelativeEntryPath(const char* absoluteSourcePath) const
491 {
492 	if (strncmp(absoluteSourcePath, fAbsoluteSourcePath,
493 			fAbsoluteSourcePath.Length()) != 0) {
494 		return absoluteSourcePath;
495 	}
496 
497 	const char* relativePath
498 		= absoluteSourcePath + fAbsoluteSourcePath.Length();
499 	return relativePath[0] == '/' ? relativePath + 1 : relativePath;
500 }
501 
502 
503 void
_UpdateProgress()504 CopyEngine::_UpdateProgress()
505 {
506 	if (fProgressReporter == NULL)
507 		return;
508 
509 	uint64 items = 0;
510 	if (fLastItemsCopied < fItemsCopied) {
511 		items = fItemsCopied - fLastItemsCopied;
512 		fLastItemsCopied = fItemsCopied;
513 	}
514 
515 	off_t bytes = 0;
516 	if (fLastBytesRead < fBytesRead) {
517 		bytes = fBytesRead - fLastBytesRead;
518 		fLastBytesRead = fBytesRead;
519 	}
520 
521 	fProgressReporter->ItemsWritten(items, bytes, fCurrentItem,
522 		fCurrentTargetFolder);
523 }
524 
525 
526 int32
_WriteThreadEntry(void * cookie)527 CopyEngine::_WriteThreadEntry(void* cookie)
528 {
529 	CopyEngine* engine = (CopyEngine*)cookie;
530 	engine->_WriteThread();
531 	return B_OK;
532 }
533 
534 
535 void
_WriteThread()536 CopyEngine::_WriteThread()
537 {
538 	bigtime_t bufferWaitTimeout = 100000;
539 
540 	while (!fQuitting) {
541 
542 		bigtime_t now = system_time();
543 
544 		Buffer* buffer = NULL;
545 		status_t ret = fBufferQueue.Pop(&buffer, bufferWaitTimeout);
546 		if (ret == B_TIMED_OUT) {
547 			// no buffer, timeout
548 			continue;
549 		} else if (ret == B_NO_INIT) {
550 			// real error
551 			return;
552 		} else if (ret != B_OK) {
553 			// no buffer, queue error
554 			snooze(10000);
555 			continue;
556 		}
557 
558 		if (!buffer->deleteFile) {
559 			ssize_t written = buffer->file->Write(buffer->buffer,
560 				buffer->validBytes);
561 			if (written != (ssize_t)buffer->validBytes) {
562 				// TODO: this should somehow be propagated back
563 				// to the main thread!
564 				fprintf(stderr, "Failed to write data: %s\n",
565 					strerror((status_t)written));
566 			}
567 			fBytesWritten += written;
568 		}
569 
570 		delete buffer;
571 
572 		// measure performance
573 		fTimeWritten += system_time() - now;
574 	}
575 
576 	double megaBytes = (double)fBytesWritten / (1024 * 1024);
577 	double seconds = (double)fTimeWritten / 1000000;
578 	if (seconds > 0) {
579 		printf("%.2f MB written (%.2f MB/s)\n", megaBytes,
580 			megaBytes / seconds);
581 	}
582 }
583