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
28using std::nothrow;
29
30
31CopyEngine::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
68CopyEngine::~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
81void
82CopyEngine::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
117status_t
118CopyEngine::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
128status_t
129CopyEngine::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
137status_t
138CopyEngine::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
144printf("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
222status_t
223CopyEngine::_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
280status_t
281CopyEngine::_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
460status_t
461CopyEngine::_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
484void
485CopyEngine::_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
507bool
508CopyEngine::_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
549bool
550CopyEngine::_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
567int32
568CopyEngine::_WriteThreadEntry(void* cookie)
569{
570	CopyEngine* engine = (CopyEngine*)cookie;
571	engine->_WriteThread();
572	return B_OK;
573}
574
575
576void
577CopyEngine::_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