1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
22IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN
23CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered
30trademarks of Be Incorporated in the United States and other countries. Other
31brand product names are registered trademarks or trademarks of their
32respective holders. All rights reserved.
33*/
34
35// Tracker file system calls.
36
37// APIs/code in FSUtils.h and FSUtils.cpp is slated for a major cleanup -- in
38// other words, you will find a lot of ugly cruft in here
39
40// ToDo:
41// Move most of preflight error checks to the Model level and only keep those
42// that have to do with size, reading/writing and name collisions.
43// Get rid of all the BList based APIs, use BObjectLists.
44// Clean up the error handling, push most of the user interaction out of the
45// low level FS calls.
46
47
48#include <ctype.h>
49#include <errno.h>
50#include <strings.h>
51#include <unistd.h>
52
53#include <Alert.h>
54#include <Application.h>
55#include <Catalog.h>
56#include <Debug.h>
57#include <Directory.h>
58#include <Entry.h>
59#include <FindDirectory.h>
60#include <Locale.h>
61#include <NodeInfo.h>
62#include <Path.h>
63#include <Roster.h>
64#include <Screen.h>
65#include <String.h>
66#include <StringFormat.h>
67#include <SymLink.h>
68#include <Volume.h>
69#include <VolumeRoster.h>
70
71#include <fs_attr.h>
72#include <fs_info.h>
73#include <sys/utsname.h>
74
75#include <AutoLocker.h>
76#include <libroot/libroot_private.h>
77#include <system/syscalls.h>
78#include <system/syscall_load_image.h>
79
80#include "Attributes.h"
81#include "Bitmaps.h"
82#include "Commands.h"
83#include "FindPanel.h"
84#include "FSUndoRedo.h"
85#include "FSUtils.h"
86#include "InfoWindow.h"
87#include "MimeTypes.h"
88#include "OverrideAlert.h"
89#include "StatusWindow.h"
90#include "Thread.h"
91#include "Tracker.h"
92#include "TrackerSettings.h"
93#include "Utilities.h"
94#include "VirtualDirectoryManager.h"
95
96
97enum {
98	kUserCanceled = B_ERRORS_END + 1,
99	kCopyCanceled = kUserCanceled,
100	kTrashCanceled
101};
102
103enum ConflictCheckResult {
104	kCanceled = kUserCanceled,
105	kPrompt,
106	kSkipAll,
107	kReplace,
108	kReplaceAll,
109	kNoConflicts
110};
111
112
113namespace BPrivate {
114
115#undef B_TRANSLATION_CONTEXT
116#define B_TRANSLATION_CONTEXT "FSUtils"
117
118static status_t FSDeleteFolder(BEntry*, CopyLoopControl*, bool updateStatus,
119	bool deleteTopDir = true, bool upateFileNameInStatus = false);
120static status_t MoveEntryToTrash(BEntry*, BPoint*, Undo &undo);
121static void LowLevelCopy(BEntry*, StatStruct*, BDirectory*, char* destName,
122	CopyLoopControl*, BPoint*);
123status_t DuplicateTask(BObjectList<entry_ref>* srcList);
124static status_t MoveTask(BObjectList<entry_ref>*, BEntry*, BList*, uint32);
125static status_t _DeleteTask(BObjectList<entry_ref>*, bool);
126static status_t _RestoreTask(BObjectList<entry_ref>*);
127status_t CalcItemsAndSize(CopyLoopControl* loopControl,
128	BObjectList<entry_ref>* refList, ssize_t blockSize, int32* totalCount,
129	off_t* totalSize);
130status_t MoveItem(BEntry* entry, BDirectory* destDir, BPoint* loc,
131	uint32 moveMode, const char* newName, Undo &undo,
132	CopyLoopControl* loopControl);
133ConflictCheckResult PreFlightNameCheck(BObjectList<entry_ref>* srcList,
134	const BDirectory* destDir, int32* collisionCount, uint32 moveMode);
135status_t CheckName(uint32 moveMode, const BEntry* srcEntry,
136	const BDirectory* destDir, bool multipleCollisions,
137	ConflictCheckResult &);
138void CopyAttributes(CopyLoopControl* control, BNode* srcNode,
139	BNode* destNode, void* buffer, size_t bufsize);
140void CopyPoseLocation(BNode* src, BNode* dest);
141bool DirectoryMatchesOrContains(const BEntry*, directory_which);
142bool DirectoryMatchesOrContains(const BEntry*, const char* additionalPath,
143	directory_which);
144bool DirectoryMatches(const BEntry*, directory_which);
145bool DirectoryMatches(const BEntry*, const char* additionalPath,
146	directory_which);
147
148status_t empty_trash(void*);
149
150
151static const char* kDeleteConfirmationStr =
152	B_TRANSLATE_MARK("Are you sure you want to delete the "
153	"selected item(s)? This operation cannot be reverted.");
154
155static const char* kReplaceStr =
156	B_TRANSLATE_MARK("You are trying to replace the item:\n"
157	"\t%name%dest\n"
158	"with:\n"
159	"\t%name%src\n\n"
160	"Would you like to replace it with the one you are %movemode?");
161
162static const char* kDirectoryReplaceStr =
163	B_TRANSLATE_MARK("An item named \"%name\" already exists in "
164	"this folder, and may contain\nitems with the same names. Would you like "
165	"to replace them with those contained in the folder you are %verb?");
166
167static const char* kSymLinkReplaceStr =
168	B_TRANSLATE_MARK("An item named \"%name\" already exists in this "
169	"folder. Would you like to replace it with the symbolic link you are "
170	"creating?");
171
172static const char* kNoFreeSpace =
173	B_TRANSLATE_MARK("Sorry, there is not enough free space on the "
174	"destination volume to copy the selection.");
175
176static const char* kFileErrorString =
177	B_TRANSLATE_MARK("Error copying file \"%name\":\n\t%error\n\n"
178	"Would you like to continue?");
179
180static const char* kFolderErrorString =
181	B_TRANSLATE_MARK("Error copying folder \"%name\":\n\t%error\n\n"
182	"Would you like to continue?");
183
184static const char* kFileDeleteErrorString =
185	B_TRANSLATE_MARK("There was an error deleting \"%name\""
186	":\n\t%error");
187
188static const char* kReplaceManyStr =
189	B_TRANSLATE_MARK("Some items already exist in this folder with "
190	"the same names as the items you are %verb.\n \nWould you like to "
191	"replace them with the ones you are %verb or be prompted for each "
192	"one?");
193
194static const char* kFindAlternativeStr =
195	B_TRANSLATE_MARK("Would you like to find some other suitable "
196	"application?");
197
198static const char* kFindApplicationStr =
199	B_TRANSLATE_MARK("Would you like to find a suitable application "
200	"to open the file?");
201
202
203// Skip these attributes when copying in Tracker
204const char* kSkipAttributes[] = {
205	kAttrPoseInfo,
206	NULL
207};
208
209
210// #pragma mark - CopyLoopControl
211
212
213CopyLoopControl::~CopyLoopControl()
214{
215}
216
217
218void
219CopyLoopControl::Init(uint32 jobKind)
220{
221}
222
223
224void
225CopyLoopControl::Init(int32 totalItems, off_t totalSize,
226	const entry_ref* destDir, bool showCount)
227{
228}
229
230
231bool
232CopyLoopControl::FileError(const char* message, const char* name,
233	status_t error, bool allowContinue)
234{
235	return false;
236}
237
238
239void
240CopyLoopControl::UpdateStatus(const char* name, const entry_ref& ref,
241	int32 count, bool optional)
242{
243}
244
245
246bool
247CopyLoopControl::CheckUserCanceled()
248{
249	return false;
250}
251
252
253CopyLoopControl::OverwriteMode
254CopyLoopControl::OverwriteOnConflict(const BEntry* srcEntry,
255	const char* destName, const BDirectory* destDir, bool srcIsDir,
256	bool dstIsDir)
257{
258	return kReplace;
259}
260
261
262bool
263CopyLoopControl::SkipEntry(const BEntry*, bool)
264{
265	// Tracker makes no exceptions
266	return false;
267}
268
269
270void
271CopyLoopControl::ChecksumChunk(const char*, size_t)
272{
273}
274
275
276bool
277CopyLoopControl::ChecksumFile(const entry_ref*)
278{
279	return true;
280}
281
282
283bool
284CopyLoopControl::SkipAttribute(const char*)
285{
286	return false;
287}
288
289
290bool
291CopyLoopControl::PreserveAttribute(const char*)
292{
293	return false;
294}
295
296
297// #pragma mark - TrackerCopyLoopControl
298
299
300TrackerCopyLoopControl::TrackerCopyLoopControl()
301	:
302	fThread(find_thread(NULL)),
303	fSourceList(NULL)
304{
305}
306
307
308TrackerCopyLoopControl::TrackerCopyLoopControl(uint32 jobKind)
309	:
310	fThread(find_thread(NULL)),
311	fSourceList(NULL)
312{
313	Init(jobKind);
314}
315
316
317TrackerCopyLoopControl::TrackerCopyLoopControl(int32 totalItems,
318		off_t totalSize)
319	:
320	fThread(find_thread(NULL)),
321	fSourceList(NULL)
322{
323	Init(totalItems, totalSize);
324}
325
326
327TrackerCopyLoopControl::~TrackerCopyLoopControl()
328{
329	if (gStatusWindow != NULL)
330		gStatusWindow->RemoveStatusItem(fThread);
331}
332
333
334void
335TrackerCopyLoopControl::Init(uint32 jobKind)
336{
337	if (gStatusWindow != NULL)
338		gStatusWindow->CreateStatusItem(fThread, (StatusWindowState)jobKind);
339}
340
341
342void
343TrackerCopyLoopControl::Init(int32 totalItems, off_t totalSize,
344	const entry_ref* destDir, bool showCount)
345{
346	if (gStatusWindow != NULL) {
347		gStatusWindow->InitStatusItem(fThread, totalItems, totalSize,
348			destDir, showCount);
349	}
350}
351
352
353bool
354TrackerCopyLoopControl::FileError(const char* message, const char* name,
355	status_t error, bool allowContinue)
356{
357	BString buffer(message);
358	buffer.ReplaceFirst("%name", name);
359	buffer.ReplaceFirst("%error", strerror(error));
360
361	if (allowContinue) {
362		BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"),
363			B_TRANSLATE("OK"), 0, B_WIDTH_AS_USUAL, B_STOP_ALERT);
364		alert->SetShortcut(0, B_ESCAPE);
365		return alert->Go() != 0;
366	}
367
368	BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"), 0, 0,
369		B_WIDTH_AS_USUAL, B_STOP_ALERT);
370	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
371	alert->Go();
372	return false;
373}
374
375
376void
377TrackerCopyLoopControl::UpdateStatus(const char* name, const entry_ref&,
378	int32 count, bool optional)
379{
380	if (gStatusWindow != NULL)
381		gStatusWindow->UpdateStatus(fThread, name, count, optional);
382}
383
384
385bool
386TrackerCopyLoopControl::CheckUserCanceled()
387{
388	if (gStatusWindow == NULL)
389		return false;
390
391	if (gStatusWindow->CheckCanceledOrPaused(fThread))
392		return true;
393
394	if (fSourceList != NULL) {
395		// TODO: Check if the user dropped additional files onto this job.
396//		printf("%p->CheckUserCanceled()\n", this);
397	}
398
399	return false;
400}
401
402
403bool
404TrackerCopyLoopControl::SkipAttribute(const char* attributeName)
405{
406	for (const char** skipAttribute = kSkipAttributes; *skipAttribute;
407		skipAttribute++) {
408		if (strcmp(*skipAttribute, attributeName) == 0)
409			return true;
410	}
411
412	return false;
413}
414
415
416void
417TrackerCopyLoopControl::SetSourceList(EntryList* list)
418{
419	fSourceList = list;
420}
421
422
423// #pragma mark - the rest
424
425
426static BNode*
427GetWritableNode(BEntry* entry, StatStruct* statBuf = 0)
428{
429	// utility call that works around the problem with BNodes not being
430	// universally writeable
431	// BNodes created on files will fail to WriteAttr because they do not
432	// have the right r/w permissions
433
434	StatStruct localStatbuf;
435
436	if (!statBuf) {
437		statBuf = &localStatbuf;
438		if (entry->GetStat(statBuf) != B_OK)
439			return 0;
440	}
441
442	if (S_ISREG(statBuf->st_mode))
443		return new BFile(entry, O_RDWR);
444
445	return new BNode(entry);
446}
447
448
449bool
450CheckDevicesEqual(const entry_ref* srcRef, const Model* targetModel)
451{
452	BDirectory destDir (targetModel->EntryRef());
453	struct stat deststat;
454	destDir.GetStat(&deststat);
455
456	return srcRef->device == deststat.st_dev;
457}
458
459
460status_t
461FSSetPoseLocation(ino_t destDirInode, BNode* destNode, BPoint point)
462{
463	PoseInfo poseInfo;
464	poseInfo.fInvisible = false;
465	poseInfo.fInitedDirectory = destDirInode;
466	poseInfo.fLocation = point;
467
468	status_t result = destNode->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
469		&poseInfo, sizeof(poseInfo));
470
471	if (result == sizeof(poseInfo))
472		return B_OK;
473
474	return result;
475}
476
477
478status_t
479FSSetPoseLocation(BEntry* entry, BPoint point)
480{
481	BNode node(entry);
482	status_t result = node.InitCheck();
483	if (result != B_OK)
484		return result;
485
486	BDirectory parent;
487	result = entry->GetParent(&parent);
488	if (result != B_OK)
489		return result;
490
491	node_ref destNodeRef;
492	result = parent.GetNodeRef(&destNodeRef);
493	if (result != B_OK)
494		return result;
495
496	return FSSetPoseLocation(destNodeRef.node, &node, point);
497}
498
499
500bool
501FSGetPoseLocation(const BNode* node, BPoint* point)
502{
503	PoseInfo poseInfo;
504	if (ReadAttr(node, kAttrPoseInfo, kAttrPoseInfoForeign,
505		B_RAW_TYPE, 0, &poseInfo, sizeof(poseInfo), &PoseInfo::EndianSwap)
506			== kReadAttrFailed) {
507		return false;
508	}
509
510	if (poseInfo.fInitedDirectory == -1LL)
511		return false;
512
513	*point = poseInfo.fLocation;
514
515	return true;
516}
517
518
519static void
520SetupPoseLocation(ino_t sourceParentIno, ino_t destParentIno,
521	const BNode* sourceNode, BNode* destNode, BPoint* loc)
522{
523	BPoint point;
524	if (loc == NULL
525		// we don't have a position yet
526		&& sourceParentIno != destParentIno
527		// we aren't  copying into the same directory
528		&& FSGetPoseLocation(sourceNode, &point)) {
529		// the original has a valid inited location
530		loc = &point;
531		// copy the originals location
532	}
533
534	if (loc != NULL && loc != (BPoint*)-1) {
535		// loc of -1 is used when copying/moving into a window in list mode
536		// where copying positions would not work
537		// ToSo:
538		// should push all this logic to upper levels
539		FSSetPoseLocation(destParentIno, destNode, *loc);
540	}
541}
542
543
544void
545FSMoveToFolder(BObjectList<entry_ref>* srcList, BEntry* destEntry,
546	uint32 moveMode, BList* pointList)
547{
548	if (srcList->IsEmpty()) {
549		delete srcList;
550		delete pointList;
551		delete destEntry;
552		return;
553	}
554
555	LaunchInNewThread("MoveTask", B_NORMAL_PRIORITY, MoveTask, srcList,
556		destEntry, pointList, moveMode);
557}
558
559
560void
561FSDelete(entry_ref* ref, bool async, bool confirm)
562{
563	BObjectList<entry_ref>* list = new BObjectList<entry_ref>(1, true);
564	list->AddItem(ref);
565	FSDeleteRefList(list, async, confirm);
566}
567
568
569void
570FSDeleteRefList(BObjectList<entry_ref>* list, bool async, bool confirm)
571{
572	if (async) {
573		LaunchInNewThread("DeleteTask", B_NORMAL_PRIORITY, _DeleteTask, list,
574			confirm);
575	} else
576		_DeleteTask(list, confirm);
577}
578
579
580void
581FSRestoreRefList(BObjectList<entry_ref>* list, bool async)
582{
583	if (async) {
584		LaunchInNewThread("RestoreTask", B_NORMAL_PRIORITY, _RestoreTask,
585			list);
586	} else
587		_RestoreTask(list);
588}
589
590
591void
592FSMoveToTrash(BObjectList<entry_ref>* srcList, BList* pointList, bool async)
593{
594	if (srcList->IsEmpty()) {
595		delete srcList;
596		delete pointList;
597		return;
598	}
599
600	if (async)
601		LaunchInNewThread("MoveTask", B_NORMAL_PRIORITY, MoveTask, srcList,
602			(BEntry*)0, pointList, kMoveSelectionTo);
603	else
604		MoveTask(srcList, 0, pointList, kMoveSelectionTo);
605}
606
607
608enum {
609	kNotConfirmed,
610	kConfirmedHomeMove,
611	kConfirmedAll
612};
613
614
615bool
616ConfirmChangeIfWellKnownDirectory(const BEntry* entry, DestructiveAction action,
617	bool dontAsk, int32* confirmedAlready)
618{
619	// Don't let the user casually move/change important files/folders
620	//
621	// This is a cheap replacement for having a real UID support turned
622	// on and not running as root all the time
623
624	if (confirmedAlready && *confirmedAlready == kConfirmedAll)
625		return true;
626
627	if (FSIsDeskDir(entry) || FSIsTrashDir(entry) || FSIsRootDir(entry))
628		return false;
629
630	if ((!DirectoryMatchesOrContains(entry, B_SYSTEM_DIRECTORY)
631		&& !DirectoryMatchesOrContains(entry, B_USER_DIRECTORY))
632		|| DirectoryMatchesOrContains(entry, B_SYSTEM_TEMP_DIRECTORY))
633		// quick way out
634		return true;
635
636	BString warning;
637	bool requireOverride = true;
638
639	if (DirectoryMatchesOrContains(entry, B_SYSTEM_DIRECTORY)) {
640		if (action == kRename) {
641			warning.SetTo(
642				B_TRANSLATE("If you rename the system folder or its "
643				"contents, you won't be able to boot %osName!\n\nAre you sure "
644				"you want to do this?\n\nTo rename the system folder or its "
645				"contents anyway, hold down the Shift key and click "
646				"\"Rename\"."));
647		} else if(action == kMove) {
648			warning.SetTo(
649				B_TRANSLATE("If you move the system folder or its "
650				"contents, you won't be able to boot %osName!\n\nAre you sure "
651				"you want to do this?\n\nTo move the system folder or its "
652				"contents anyway, hold down the Shift key and click "
653				"\"Move\"."));
654		} else {
655			warning.SetTo(
656				B_TRANSLATE("If you alter the system folder or its "
657				"contents, you won't be able to boot %osName!\n\nAre you sure "
658				"you want to do this?\n\nTo alter the system folder or its "
659				"contents anyway, hold down the Shift key and click "
660				"\"I know what I'm doing\"."));
661		}
662	} else if (DirectoryMatches(entry, B_USER_DIRECTORY)) {
663		if (action == kRename) {
664			warning .SetTo(
665				B_TRANSLATE("If you rename the home folder, %osName "
666				"may not behave properly!\n\nAre you sure you want to do this?"
667				"\n\nTo rename the home folder anyway, hold down the "
668				"Shift key and click \"Rename\"."));
669		} else if (action == kMove) {
670			warning .SetTo(
671				B_TRANSLATE("If you move the home folder, %osName "
672				"may not behave properly!\n\nAre you sure you want to do this?"
673				"\n\nTo move the home folder anyway, hold down the "
674				"Shift key and click \"Move\"."));
675		} else {
676			warning .SetTo(
677				B_TRANSLATE("If you alter the home folder, %osName "
678				"may not behave properly!\n\nAre you sure you want to do this?"
679				"\n\nTo alter the home folder anyway, hold down the "
680				"Shift key and click \"I know what I'm doing\"."));
681		}
682	} else if (DirectoryMatchesOrContains(entry, B_USER_CONFIG_DIRECTORY)
683		|| DirectoryMatchesOrContains(entry, B_SYSTEM_SETTINGS_DIRECTORY)) {
684		if (action == kRename) {
685			warning.SetTo(
686				B_TRANSLATE("If you rename %target, %osName may not behave "
687				"properly!\n\nAre you sure you want to do this?"));
688		} else if (action == kMove) {
689			warning.SetTo(
690				B_TRANSLATE("If you move %target, %osName may not behave "
691				"properly!\n\nAre you sure you want to do this?"));
692		} else {
693			warning.SetTo(
694				B_TRANSLATE("If you alter %target, %osName may not behave "
695				"properly!\n\nAre you sure you want to do this?"));
696		}
697
698		if (DirectoryMatchesOrContains(entry, "beos_mime",
699				B_USER_SETTINGS_DIRECTORY)
700			|| DirectoryMatchesOrContains(entry, "beos_mime",
701				B_SYSTEM_SETTINGS_DIRECTORY)) {
702			warning.ReplaceFirst("%target", B_TRANSLATE("the MIME settings"));
703			requireOverride = false;
704		} else if (DirectoryMatches(entry, B_USER_CONFIG_DIRECTORY)) {
705			warning.ReplaceFirst("%target", B_TRANSLATE("the config folder"));
706			requireOverride = false;
707		} else if (DirectoryMatches(entry, B_USER_SETTINGS_DIRECTORY)
708			|| DirectoryMatches(entry, B_SYSTEM_SETTINGS_DIRECTORY)) {
709			warning.ReplaceFirst("%target", B_TRANSLATE("the settings folder"));
710			requireOverride = false;
711		} else {
712			// It was not a special directory/file after all. Allow renaming.
713			return true;
714		}
715	} else
716		return true;
717
718	if (dontAsk)
719		return false;
720
721	if (confirmedAlready && *confirmedAlready == kConfirmedHomeMove
722		&& !requireOverride)
723		// we already warned about moving home this time around
724		return true;
725
726	struct utsname name;
727	if (uname(&name) == -1)
728		warning.ReplaceFirst("%osName", "Haiku");
729	else
730		warning.ReplaceFirst("%osName", name.sysname);
731
732	BString buttonLabel;
733	if (action == kRename) {
734		buttonLabel = B_TRANSLATE_COMMENT("Rename", "button label");
735	} else if (action == kMove) {
736		buttonLabel = B_TRANSLATE_COMMENT("Move", "button label");
737	} else {
738		buttonLabel = B_TRANSLATE_COMMENT("I know what I'm doing",
739			"button label");
740	}
741
742	OverrideAlert* alert = new OverrideAlert("", warning.String(),
743		buttonLabel.String(), (requireOverride ? B_SHIFT_KEY : 0),
744		B_TRANSLATE("Cancel"), 0, NULL, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
745	alert->SetShortcut(1, B_ESCAPE);
746	if (alert->Go() == 1) {
747		if (confirmedAlready)
748			*confirmedAlready = kNotConfirmed;
749		return false;
750	}
751
752	if (confirmedAlready) {
753		if (!requireOverride)
754			*confirmedAlready = kConfirmedHomeMove;
755		else
756			*confirmedAlready = kConfirmedAll;
757	}
758
759	return true;
760}
761
762
763status_t
764EditModelName(const Model* model, const char* name, size_t length)
765{
766	if (model == NULL || name == NULL || name[0] == '\0' || length <= 0)
767		return B_BAD_VALUE;
768
769	BEntry entry(model->EntryRef());
770	status_t result = entry.InitCheck();
771	if (result != B_OK)
772		return result;
773
774	// TODO: use model-flavor specific virtuals for these special renamings
775
776	if (model->HasLocalizedName() || model->IsDesktop() || model->IsRoot()
777		|| model->IsTrash() || model->IsVirtualDirectory()) {
778		result = B_NOT_ALLOWED;
779	} else if (model->IsQuery()) {
780		// write to query parameter
781		BModelWriteOpener opener(const_cast<Model*>(model));
782		ASSERT(model->Node());
783		MoreOptionsStruct::SetQueryTemporary(model->Node(), false);
784
785		RenameUndo undo(entry, name);
786		result = entry.Rename(name);
787		if (result != B_OK)
788			undo.Remove();
789	} else if (model->IsVolume()) {
790		// write volume name
791		BVolume volume(model->NodeRef()->device);
792		result = volume.InitCheck();
793		if (result == B_OK && volume.IsReadOnly())
794			result = B_READ_ONLY_DEVICE;
795		if (result == B_OK) {
796			RenameVolumeUndo undo(volume, name);
797			result = volume.SetName(name);
798			if (result != B_OK)
799				undo.Remove();
800		}
801	} else {
802		BVolume volume(model->NodeRef()->device);
803		result = volume.InitCheck();
804		if (result == B_OK && volume.IsReadOnly())
805			result = B_READ_ONLY_DEVICE;
806		if (result == B_OK)
807			result = ShouldEditRefName(model->EntryRef(), name, length);
808		if (result == B_OK) {
809			RenameUndo undo(entry, name);
810			result = entry.Rename(name);
811			if (result != B_OK)
812				undo.Remove();
813		}
814	}
815
816	return result;
817}
818
819
820status_t
821ShouldEditRefName(const entry_ref* ref, const char* name, size_t length)
822{
823	if (ref == NULL || name == NULL || name[0] == '\0' || length <= 0)
824		return B_BAD_VALUE;
825
826	BEntry entry(ref);
827	if (entry.InitCheck() != B_OK)
828		return B_NO_INIT;
829
830	// check if name is too long
831	if (length >= B_FILE_NAME_LENGTH) {
832		BString text;
833		if (entry.IsDirectory())
834			text = B_TRANSLATE("The entered folder name is too long.");
835		else
836			text = B_TRANSLATE("The entered file name is too long.");
837
838		BAlert* alert = new BAlert("", text, B_TRANSLATE("OK"),
839			0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
840		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
841		alert->Go();
842
843		return B_NAME_TOO_LONG;
844	}
845
846	// same name
847	if (strcmp(name, ref->name) == 0)
848		return B_OK;
849
850	// user declined rename in system directory
851	if (!ConfirmChangeIfWellKnownDirectory(&entry, kRename))
852		return B_CANCELED;
853
854	// entry must have a parent directory
855	BDirectory parent;
856	if (entry.GetParent(&parent) != B_OK)
857		return B_ERROR;
858
859	// check for name conflict
860	if (parent.Contains(name)) {
861		BString text(B_TRANSLATE("An item named '%filename%' already exists."));
862		text.ReplaceFirst("%filename%", name);
863
864		BAlert* alert = new BAlert("", text, B_TRANSLATE("OK"),
865			0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
866		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
867		alert->Go();
868
869		return B_NAME_IN_USE;
870	}
871
872	// success
873	return B_OK;
874}
875
876
877static status_t
878InitCopy(CopyLoopControl* loopControl, uint32 moveMode,
879	BObjectList<entry_ref>* srcList, BVolume* dstVol, BDirectory* destDir,
880	entry_ref* destRef, bool preflightNameCheck, bool needSizeCalculation,
881	int32* collisionCount, ConflictCheckResult* preflightResult)
882{
883	if (dstVol->IsReadOnly())
884		return B_READ_ONLY_DEVICE;
885
886	int32 numItems = srcList->CountItems();
887	int32 askOnceOnly = kNotConfirmed;
888	for (int32 index = 0; index < numItems; index++) {
889		// we could check for this while iterating through items in each of
890		// the copy loops, except it takes forever to call CalcItemsAndSize
891		BEntry entry((entry_ref*)srcList->ItemAt(index));
892		if (FSIsRootDir(&entry)) {
893			BString errorStr;
894			if (moveMode == kCreateLink) {
895				errorStr.SetTo(
896					B_TRANSLATE("You cannot create a link to the root "
897					"directory."));
898			} else {
899				errorStr.SetTo(
900					B_TRANSLATE("You cannot copy or move the root "
901					"directory."));
902			}
903
904			BAlert* alert = new BAlert("", errorStr.String(),
905				B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
906				B_WARNING_ALERT);
907			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
908			alert->Go();
909			return B_ERROR;
910		}
911		if (moveMode == kMoveSelectionTo
912			&& !ConfirmChangeIfWellKnownDirectory(&entry, kMove,
913				false, &askOnceOnly)) {
914			return B_ERROR;
915		}
916	}
917
918	if (preflightNameCheck) {
919		ASSERT(collisionCount);
920		ASSERT(preflightResult);
921
922		*preflightResult = kPrompt;
923		*collisionCount = 0;
924
925		*preflightResult = PreFlightNameCheck(srcList, destDir,
926			collisionCount, moveMode);
927		if (*preflightResult == kCanceled) {
928			// user canceled
929			return B_ERROR;
930		}
931	}
932
933	// set up the status display
934	switch (moveMode) {
935		case kCopySelectionTo:
936		case kDuplicateSelection:
937		case kMoveSelectionTo:
938			{
939				loopControl->Init(moveMode == kMoveSelectionTo ? kMoveState
940					: kCopyState);
941
942				int32 totalItems = 0;
943				off_t totalSize = 0;
944				if (needSizeCalculation) {
945					if (CalcItemsAndSize(loopControl, srcList,
946							dstVol->BlockSize(), &totalItems, &totalSize)
947							!= B_OK) {
948						return B_ERROR;
949					}
950
951					// check for free space before starting copy
952					if ((totalSize + (4* kKBSize)) >= dstVol->FreeBytes()) {
953						BAlert* alert = new BAlert("",
954							B_TRANSLATE_NOCOLLECT(kNoFreeSpace),
955							B_TRANSLATE("Cancel"),
956							0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
957						alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
958						alert->Go();
959						return B_ERROR;
960					}
961				}
962
963				loopControl->Init(totalItems, totalSize, destRef);
964				break;
965			}
966
967		case kCreateLink:
968			if (numItems > 10) {
969				// this will be fast, only put up status if lots of items
970				// moved, links created
971				loopControl->Init(kCreateLinkState);
972				loopControl->Init(numItems, numItems, destRef);
973			}
974			break;
975	}
976
977	return B_OK;
978}
979
980
981// ToDo:
982// get rid of this cruft
983bool
984delete_ref(void* ref)
985{
986	delete (entry_ref*)ref;
987	return false;
988}
989
990
991bool
992delete_point(void* point)
993{
994	delete (BPoint*)point;
995	return false;
996}
997
998
999static status_t
1000MoveTask(BObjectList<entry_ref>* srcList, BEntry* destEntry, BList* pointList,
1001	uint32 moveMode)
1002{
1003	ASSERT(!srcList->IsEmpty());
1004
1005	// extract information from src, dest models
1006	// ## note that we're assuming all items come from the same volume
1007	// ## by looking only at FirstItem here which is not a good idea
1008	dev_t srcVolumeDevice = srcList->FirstItem()->device;
1009	dev_t destVolumeDevice = srcVolumeDevice;
1010
1011	StatStruct deststat;
1012	BVolume volume(srcVolumeDevice);
1013	entry_ref destRef;
1014
1015	bool destIsTrash = false;
1016	BDirectory destDir;
1017	BDirectory* destDirToCheck = NULL;
1018	bool needPreflightNameCheck = false;
1019	bool sourceIsReadOnly = volume.IsReadOnly();
1020	volume.Unset();
1021
1022	bool fromUndo = FSIsUndoMoveMode(moveMode);
1023	moveMode = FSMoveMode(moveMode);
1024
1025	// if we're not passed a destEntry then we are supposed to move to trash
1026	if (destEntry != NULL) {
1027		destEntry->GetRef(&destRef);
1028
1029		destDir.SetTo(destEntry);
1030		destDir.GetStat(&deststat);
1031		destDirToCheck = &destDir;
1032
1033		destVolumeDevice = deststat.st_dev;
1034		destIsTrash = FSIsTrashDir(destEntry);
1035		volume.SetTo(destVolumeDevice);
1036
1037		needPreflightNameCheck = true;
1038	} else if (moveMode == kDuplicateSelection) {
1039		BEntry entry;
1040		entry.SetTo(srcList->FirstItem());
1041		entry.GetParent(&destDir);
1042		volume.SetTo(srcVolumeDevice);
1043	} else {
1044		// move is to trash
1045		destIsTrash = true;
1046
1047		FSGetTrashDir(&destDir, srcVolumeDevice);
1048		volume.SetTo(srcVolumeDevice);
1049
1050		BEntry entry;
1051		destDir.GetEntry(&entry);
1052		destDirToCheck = &destDir;
1053
1054		entry.GetRef(&destRef);
1055	}
1056
1057	// change the move mode if needed
1058	if (moveMode == kCopySelectionTo && destIsTrash) {
1059		// cannot copy to trash
1060		moveMode = kMoveSelectionTo;
1061	}
1062
1063	if (moveMode == kMoveSelectionTo && sourceIsReadOnly)
1064		moveMode = kCopySelectionTo;
1065
1066	bool needSizeCalculation = true;
1067	if ((moveMode == kMoveSelectionTo && srcVolumeDevice == destVolumeDevice)
1068		|| destIsTrash) {
1069		needSizeCalculation = false;
1070	}
1071
1072	// we need the undo object later on, so we create it no matter
1073	// if we really need it or not (it's very lightweight)
1074	MoveCopyUndo undo(srcList, destDir, pointList, moveMode);
1075	if (fromUndo)
1076		undo.Remove();
1077
1078	TrackerCopyLoopControl loopControl;
1079
1080	ConflictCheckResult conflictCheckResult = kPrompt;
1081	int32 collisionCount = 0;
1082	// TODO: Status item is created in InitCopy(), but it would be kind of
1083	// neat to move all that into TrackerCopyLoopControl
1084	status_t result = InitCopy(&loopControl, moveMode, srcList,
1085		&volume, destDirToCheck, &destRef, needPreflightNameCheck,
1086		needSizeCalculation, &collisionCount, &conflictCheckResult);
1087
1088	loopControl.SetSourceList(srcList);
1089
1090	if (result == B_OK) {
1091		for (int32 i = 0; i < srcList->CountItems(); i++) {
1092			BPoint* loc = (BPoint*)-1;
1093				// a loc of -1 forces autoplacement, rather than copying the
1094				// position of the original node
1095				// TODO:
1096				// Clean this mess up!
1097				// What could be a cleaner design is to pass along some kind
1098				// "filter" object that post-processes poses, i.e. adds the
1099				// location or other stuff. It should not be a job of the
1100				// copy-engine.
1101
1102			entry_ref* srcRef = srcList->ItemAt(i);
1103
1104			if (moveMode == kDuplicateSelection) {
1105				BEntry entry(srcRef);
1106				entry.GetParent(&destDir);
1107				destDir.GetStat(&deststat);
1108				volume.SetTo(srcRef->device);
1109			}
1110
1111			// handle case where item is dropped into folder it already lives
1112			// in which could happen if dragging from a query window
1113			if (moveMode != kCreateLink
1114				&& moveMode != kCreateRelativeLink
1115				&& moveMode != kDuplicateSelection
1116				&& !destIsTrash
1117				&& (srcRef->device == destRef.device
1118				&& srcRef->directory == deststat.st_ino)) {
1119				continue;
1120			}
1121
1122			if (loopControl.CheckUserCanceled())
1123				break;
1124
1125			BEntry sourceEntry(srcRef);
1126			if (sourceEntry.InitCheck() != B_OK) {
1127				BString error(B_TRANSLATE("Error moving \"%name\"."));
1128				error.ReplaceFirst("%name", srcRef->name);
1129				BAlert* alert = new BAlert("", error.String(),
1130					B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
1131					B_WARNING_ALERT);
1132				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1133				alert->Go();
1134				break;
1135			}
1136
1137			// are we moving item to trash?
1138			if (destIsTrash) {
1139				if (pointList != NULL)
1140					loc = (BPoint*)pointList->ItemAt(i);
1141
1142				result = MoveEntryToTrash(&sourceEntry, loc, undo);
1143				if (result != B_OK) {
1144					BString error(B_TRANSLATE("Error moving \"%name\" to Trash. "
1145						"(%error)"));
1146					error.ReplaceFirst("%name", srcRef->name);
1147					error.ReplaceFirst("%error", strerror(result));
1148					BAlert* alert = new BAlert("", error.String(),
1149						B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
1150						B_WARNING_ALERT);
1151					alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1152					alert->Go();
1153					break;
1154				}
1155				continue;
1156			}
1157
1158			// resolve name collisions and hierarchy problems
1159			if (CheckName(moveMode, &sourceEntry, &destDir,
1160					collisionCount > 1, conflictCheckResult) != B_OK) {
1161				// we will skip the current item, because we got a conflict
1162				// and were asked to or because there was some conflict
1163
1164				// update the status because item got skipped and the status
1165				// will not get updated by the move call
1166				loopControl.UpdateStatus(srcRef->name, *srcRef, 1);
1167
1168				continue;
1169			}
1170
1171			// get location to place this item
1172			if (pointList && moveMode != kCopySelectionTo) {
1173				loc = (BPoint*)pointList->ItemAt(i);
1174
1175				BNode* src_node = GetWritableNode(&sourceEntry);
1176				if (src_node && src_node->InitCheck() == B_OK) {
1177					PoseInfo poseInfo;
1178					poseInfo.fInvisible = false;
1179					poseInfo.fInitedDirectory = deststat.st_ino;
1180					poseInfo.fLocation = *loc;
1181					src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
1182						&poseInfo, sizeof(poseInfo));
1183				}
1184				delete src_node;
1185			}
1186
1187			if (pointList)
1188 				loc = (BPoint*)pointList->ItemAt(i);
1189
1190			result = MoveItem(&sourceEntry, &destDir, loc, moveMode, NULL,
1191				undo, &loopControl);
1192			if (result != B_OK)
1193				break;
1194		}
1195	}
1196
1197	// duplicates of srcList, destFolder were created - dispose them
1198	delete srcList;
1199	delete destEntry;
1200
1201	// delete file location list and all Points within
1202	if (pointList != NULL) {
1203		pointList->DoForEach(delete_point);
1204		delete pointList;
1205	}
1206
1207	return B_OK;
1208}
1209
1210
1211class FailWithAlert {
1212	public:
1213		static void FailOnError(status_t error, const char* string,
1214			const char* name = NULL)
1215		{
1216			if (error != B_OK)
1217				throw FailWithAlert(error, string, name);
1218		}
1219
1220		FailWithAlert(status_t error, const char* string, const char* name)
1221			:
1222			fString(string),
1223			fName(name),
1224			fError(error)
1225		{
1226		}
1227
1228		const char* fString;
1229		const char* fName;
1230		status_t fError;
1231};
1232
1233
1234class MoveError {
1235public:
1236	static void FailOnError(status_t error)
1237	{
1238		if (error != B_OK)
1239			throw MoveError(error);
1240	}
1241
1242	MoveError(status_t error)
1243		:
1244		fError(error)
1245	{
1246	}
1247
1248	status_t fError;
1249};
1250
1251
1252void
1253CopyFile(BEntry* srcFile, StatStruct* srcStat, BDirectory* destDir,
1254	CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName,
1255	Undo &undo)
1256{
1257	if (loopControl->SkipEntry(srcFile, true))
1258		return;
1259
1260	node_ref node;
1261	destDir->GetNodeRef(&node);
1262	BVolume volume(node.device);
1263
1264	// check for free space first
1265	if ((srcStat->st_size + kKBSize) >= volume.FreeBytes()) {
1266		loopControl->FileError(B_TRANSLATE_NOCOLLECT(kNoFreeSpace), "",
1267			B_DEVICE_FULL, false);
1268		throw (status_t)B_DEVICE_FULL;
1269	}
1270
1271	char destName[B_FILE_NAME_LENGTH];
1272	srcFile->GetName(destName);
1273	entry_ref ref;
1274	srcFile->GetRef(&ref);
1275
1276	loopControl->UpdateStatus(destName, ref, 1024, true);
1277
1278	if (makeOriginalName) {
1279		BString suffix(" ");
1280		suffix << B_TRANSLATE_COMMENT("copy", "filename copy"),
1281		FSMakeOriginalName(destName, destDir, suffix.String());
1282		undo.UpdateEntry(srcFile, destName);
1283	}
1284
1285	BEntry conflictingEntry;
1286	if (destDir->FindEntry(destName, &conflictingEntry) == B_OK) {
1287		switch (loopControl->OverwriteOnConflict(srcFile, destName, destDir,
1288				false, false)) {
1289			case TrackerCopyLoopControl::kSkip:
1290				// we are about to ignore this entire directory
1291				return;
1292
1293			case TrackerCopyLoopControl::kReplace:
1294				if (!conflictingEntry.IsDirectory()) {
1295					ThrowOnError(conflictingEntry.Remove());
1296					break;
1297				}
1298				// fall through if not a directory
1299			case TrackerCopyLoopControl::kMerge:
1300				// This flag implies that the attributes should be kept
1301				// on the file.  Just ignore it.
1302				break;
1303		}
1304	}
1305
1306	try {
1307		LowLevelCopy(srcFile, srcStat, destDir, destName, loopControl, loc);
1308	} catch (status_t err) {
1309		if (err == kCopyCanceled)
1310			throw (status_t)err;
1311
1312		if (err != B_OK) {
1313			if (!loopControl->FileError(
1314					B_TRANSLATE_NOCOLLECT(kFileErrorString), destName, err,
1315					true)) {
1316				throw (status_t)err;
1317			} else {
1318				// user selected continue in spite of error, update status bar
1319				loopControl->UpdateStatus(NULL, ref, (int32)srcStat->st_size);
1320			}
1321		}
1322	}
1323}
1324
1325
1326#ifdef _SILENTLY_CORRECT_FILE_NAMES
1327static bool
1328CreateFileSystemCompatibleName(const BDirectory* destDir, char* destName)
1329{
1330	// Is it a FAT32 file system?
1331	// (this is the only one we currently know about)
1332
1333	BEntry target;
1334	destDir->GetEntry(&target);
1335	entry_ref targetRef;
1336	fs_info info;
1337	if (target.GetRef(&targetRef) == B_OK
1338		&& fs_stat_dev(targetRef.device, &info) == B_OK
1339		&& !strcmp(info.fsh_name, "fat")) {
1340		bool wasInvalid = false;
1341
1342		// it's a FAT32 file system, now check the name
1343
1344		int32 length = strlen(destName) - 1;
1345		while (destName[length] == '.') {
1346			// invalid name, just cut off the dot at the end
1347			destName[length--] = '\0';
1348			wasInvalid = true;
1349		}
1350
1351		char* invalid = destName;
1352		while ((invalid = strpbrk(invalid, "?<>\\:\"|*")) != NULL) {
1353			invalid[0] = '_';
1354			wasInvalid = true;
1355		}
1356
1357		return wasInvalid;
1358	}
1359
1360	return false;
1361}
1362#endif
1363
1364
1365static void
1366LowLevelCopy(BEntry* srcEntry, StatStruct* srcStat, BDirectory* destDir,
1367	char* destName, CopyLoopControl* loopControl, BPoint* loc)
1368{
1369	entry_ref ref;
1370	ThrowOnError(srcEntry->GetRef(&ref));
1371
1372	if (S_ISLNK(srcStat->st_mode)) {
1373		// handle symbolic links
1374		BSymLink srcLink;
1375		BSymLink newLink;
1376		char linkpath[MAXPATHLEN];
1377
1378		ThrowOnError(srcLink.SetTo(srcEntry));
1379		ssize_t size = srcLink.ReadLink(linkpath, MAXPATHLEN - 1);
1380		if (size < 0)
1381			ThrowOnError(size);
1382		ThrowOnError(destDir->CreateSymLink(destName, linkpath, &newLink));
1383
1384		node_ref destNodeRef;
1385		destDir->GetNodeRef(&destNodeRef);
1386		// copy or write new pose location as a first thing
1387		SetupPoseLocation(ref.directory, destNodeRef.node, &srcLink,
1388			&newLink, loc);
1389
1390		BNodeInfo nodeInfo(&newLink);
1391		nodeInfo.SetType(B_LINK_MIMETYPE);
1392
1393		newLink.SetPermissions(srcStat->st_mode);
1394		newLink.SetOwner(srcStat->st_uid);
1395		newLink.SetGroup(srcStat->st_gid);
1396		newLink.SetModificationTime(srcStat->st_mtime);
1397		newLink.SetCreationTime(srcStat->st_crtime);
1398
1399		return;
1400	}
1401
1402	BFile srcFile(srcEntry, O_RDONLY);
1403	ThrowOnInitCheckError(&srcFile);
1404
1405	const size_t kMinBufferSize = 1024* 128;
1406	const size_t kMaxBufferSize = 1024* 1024;
1407
1408	size_t bufsize = kMinBufferSize;
1409	if ((off_t)bufsize < srcStat->st_size) {
1410		// File bigger than the buffer size: determine an optimal buffer size
1411		system_info sinfo;
1412		get_system_info(&sinfo);
1413		size_t freesize = static_cast<size_t>(
1414			(sinfo.max_pages - sinfo.used_pages) * B_PAGE_SIZE);
1415		bufsize = freesize / 4;
1416			// take 1/4 of RAM max
1417		bufsize -= bufsize % (16* 1024);
1418			// Round to 16 KB boundaries
1419		if (bufsize < kMinBufferSize) {
1420			// at least kMinBufferSize
1421			bufsize = kMinBufferSize;
1422		} else if (bufsize > kMaxBufferSize) {
1423			// no more than kMaxBufferSize
1424			bufsize = kMaxBufferSize;
1425		}
1426	}
1427
1428	BFile destFile(destDir, destName, O_RDWR | O_CREAT);
1429#ifdef _SILENTLY_CORRECT_FILE_NAMES
1430	if ((destFile.InitCheck() == B_BAD_VALUE
1431		|| destFile.InitCheck() == B_NOT_ALLOWED)
1432		&& CreateFileSystemCompatibleName(destDir, destName)) {
1433		destFile.SetTo(destDir, destName, B_CREATE_FILE | B_READ_WRITE);
1434	}
1435#endif
1436
1437	ThrowOnInitCheckError(&destFile);
1438
1439	node_ref destNodeRef;
1440	destDir->GetNodeRef(&destNodeRef);
1441	// copy or write new pose location as a first thing
1442	SetupPoseLocation(ref.directory, destNodeRef.node, &srcFile,
1443		&destFile, loc);
1444
1445	char* buffer = new char[bufsize];
1446	try {
1447		// copy data portion of file
1448		while (true) {
1449			if (loopControl->CheckUserCanceled()) {
1450				// if copy was canceled, remove partial destination file
1451				destFile.Unset();
1452
1453				BEntry destEntry;
1454				if (destDir->FindEntry(destName, &destEntry) == B_OK)
1455					destEntry.Remove();
1456
1457				throw (status_t)kCopyCanceled;
1458			}
1459
1460			ASSERT(buffer);
1461			ssize_t bytes = srcFile.Read(buffer, bufsize);
1462
1463			if (bytes > 0) {
1464				ssize_t updateBytes = 0;
1465				if (bytes > 32* 1024) {
1466					// when copying large chunks, update after read and after
1467					// write to get better update granularity
1468					updateBytes = bytes / 2;
1469					loopControl->UpdateStatus(NULL, ref, updateBytes, true);
1470				}
1471
1472				loopControl->ChecksumChunk(buffer, (size_t)bytes);
1473
1474				ssize_t result = destFile.Write(buffer, (size_t)bytes);
1475				if (result != bytes) {
1476					if (result < 0)
1477						throw (status_t)result;
1478					throw (status_t)B_ERROR;
1479				}
1480
1481				result = destFile.Sync();
1482				if (result != B_OK)
1483					throw (status_t)result;
1484
1485				loopControl->UpdateStatus(NULL, ref, bytes - updateBytes,
1486					true);
1487			} else if (bytes < 0) {
1488				// read error
1489				throw (status_t)bytes;
1490			} else {
1491				// we are done
1492				break;
1493			}
1494		}
1495
1496		CopyAttributes(loopControl, &srcFile, &destFile, buffer, bufsize);
1497	} catch (...) {
1498		delete[] buffer;
1499		throw;
1500	}
1501
1502	destFile.SetPermissions(srcStat->st_mode);
1503	destFile.SetOwner(srcStat->st_uid);
1504	destFile.SetGroup(srcStat->st_gid);
1505	destFile.SetModificationTime(srcStat->st_mtime);
1506	destFile.SetCreationTime(srcStat->st_crtime);
1507
1508	delete[] buffer;
1509
1510	if (!loopControl->ChecksumFile(&ref)) {
1511		// File no good.  Remove and quit.
1512		destFile.Unset();
1513
1514		BEntry destEntry;
1515		if (destDir->FindEntry(destName, &destEntry) == B_OK)
1516			destEntry.Remove();
1517		throw (status_t)kUserCanceled;
1518	}
1519}
1520
1521
1522void
1523CopyAttributes(CopyLoopControl* control, BNode* srcNode, BNode* destNode,
1524	void* buffer, size_t bufsize)
1525{
1526	// ToDo:
1527	// Add error checking
1528	// prior to coyping attributes, make sure indices are installed
1529
1530	// When calling CopyAttributes on files, have to make sure destNode
1531	// is a BFile opened R/W
1532
1533	srcNode->RewindAttrs();
1534	char name[256];
1535	while (srcNode->GetNextAttrName(name) == B_OK) {
1536		// Check to see if this attribute should be skipped.
1537		if (control->SkipAttribute(name))
1538			continue;
1539
1540		attr_info info;
1541		if (srcNode->GetAttrInfo(name, &info) != B_OK)
1542			continue;
1543
1544		// Check to see if this attribute should be overwritten when it
1545		// already exists.
1546		if (control->PreserveAttribute(name)) {
1547			attr_info dest_info;
1548			if (destNode->GetAttrInfo(name, &dest_info) == B_OK)
1549				continue;
1550		}
1551
1552		// Special case for a size 0 attribute. It wouldn't be written at all
1553		// otherwise.
1554		if (info.size == 0)
1555			destNode->WriteAttr(name, info.type, 0, buffer, 0);
1556
1557		ssize_t bytes;
1558		ssize_t numToRead = (ssize_t)info.size;
1559		for (off_t offset = 0; numToRead > 0; offset += bytes) {
1560			size_t chunkSize = (size_t)numToRead;
1561			if (chunkSize > bufsize)
1562				chunkSize = bufsize;
1563
1564			bytes = srcNode->ReadAttr(name, info.type, offset,
1565				buffer, chunkSize);
1566
1567			if (bytes <= 0)
1568				break;
1569
1570			destNode->WriteAttr(name, info.type, offset, buffer,
1571				(size_t)bytes);
1572
1573			numToRead -= bytes;
1574		}
1575	}
1576}
1577
1578
1579static void
1580CopyFolder(BEntry* srcEntry, BDirectory* destDir,
1581	CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName,
1582	Undo &undo, bool removeSource = false)
1583{
1584	BDirectory newDir;
1585	BEntry entry;
1586	status_t err = B_OK;
1587	bool createDirectory = true;
1588	BEntry existingEntry;
1589
1590	if (loopControl->SkipEntry(srcEntry, false))
1591		return;
1592
1593	entry_ref ref;
1594	srcEntry->GetRef(&ref);
1595
1596	char destName[B_FILE_NAME_LENGTH];
1597	strlcpy(destName, ref.name, sizeof(destName));
1598
1599	loopControl->UpdateStatus(ref.name, ref, 1024, true);
1600
1601	if (makeOriginalName) {
1602		BString suffix(" ");
1603		suffix << B_TRANSLATE_COMMENT("copy", "filename copy"),
1604		FSMakeOriginalName(destName, destDir, suffix.String());
1605		undo.UpdateEntry(srcEntry, destName);
1606	}
1607
1608	if (destDir->FindEntry(destName, &existingEntry) == B_OK) {
1609		// some entry with a conflicting name is already present in destDir
1610		// decide what to do about it
1611		bool isDirectory = existingEntry.IsDirectory();
1612
1613		switch (loopControl->OverwriteOnConflict(srcEntry, destName, destDir,
1614			true, isDirectory)) {
1615			case TrackerCopyLoopControl::kSkip:
1616				// we are about to ignore this entire directory
1617				return;
1618
1619
1620			case TrackerCopyLoopControl::kReplace:
1621				if (!isDirectory) {
1622					// conflicting with a file or symbolic link, remove entry
1623					ThrowOnError(existingEntry.Remove());
1624					break;
1625				}
1626			// fall through if directory, do not replace.
1627			case TrackerCopyLoopControl::kMerge:
1628				ASSERT(isDirectory);
1629				// do not create a new directory, use the current one
1630				newDir.SetTo(&existingEntry);
1631				createDirectory = false;
1632				break;
1633		}
1634	}
1635
1636	// loop through everything in src folder and copy it to new folder
1637	BDirectory srcDir(srcEntry);
1638	srcDir.Rewind();
1639
1640	// create a new folder inside of destination folder
1641	if (createDirectory) {
1642	 	err = destDir->CreateDirectory(destName, &newDir);
1643#ifdef _SILENTLY_CORRECT_FILE_NAMES
1644	 	if (err == B_BAD_VALUE) {
1645	 		// check if it's an invalid name on a FAT32 file system
1646	 		if (CreateFileSystemCompatibleName(destDir, destName))
1647	 			err = destDir->CreateDirectory(destName, &newDir);
1648	 	}
1649#endif
1650		if (err != B_OK) {
1651			if (!loopControl->FileError(B_TRANSLATE_NOCOLLECT(
1652					kFolderErrorString), destName, err, true)) {
1653				throw err;
1654			}
1655
1656			// will allow rest of copy to continue
1657			return;
1658		}
1659	}
1660
1661	char* buffer;
1662	if (createDirectory && err == B_OK
1663		&& (buffer = (char*)malloc(32768)) != 0) {
1664		CopyAttributes(loopControl, &srcDir, &newDir, buffer, 32768);
1665			// don't copy original pose location if new location passed
1666		free(buffer);
1667	}
1668
1669	StatStruct statbuf;
1670	srcDir.GetStat(&statbuf);
1671	dev_t sourceDeviceID = statbuf.st_dev;
1672
1673	// copy or write new pose location
1674	node_ref destNodeRef;
1675	destDir->GetNodeRef(&destNodeRef);
1676	SetupPoseLocation(ref.directory, destNodeRef.node, &srcDir,
1677		&newDir, loc);
1678
1679	while (srcDir.GetNextEntry(&entry) == B_OK) {
1680
1681		if (loopControl->CheckUserCanceled())
1682			throw (status_t)kUserCanceled;
1683
1684		entry.GetStat(&statbuf);
1685
1686		if (S_ISDIR(statbuf.st_mode)) {
1687
1688			// entry is a mount point, do not copy it
1689			if (statbuf.st_dev != sourceDeviceID) {
1690				PRINT(("Avoiding mount point %" B_PRIdDEV ", %" B_PRIdDEV "\n",
1691					statbuf.st_dev, sourceDeviceID));
1692				continue;
1693			}
1694
1695			CopyFolder(&entry, &newDir, loopControl, 0, false, undo,
1696				removeSource);
1697			if (removeSource)
1698				FSDeleteFolder(&entry, loopControl, true, true, false);
1699		} else if (S_ISREG(statbuf.st_mode) || S_ISLNK(statbuf.st_mode)) {
1700			CopyFile(&entry, &statbuf, &newDir, loopControl, 0, false, undo);
1701			if (removeSource)
1702				entry.Remove();
1703		} else {
1704			// Ignore special files
1705		}
1706	}
1707	if (removeSource)
1708		srcEntry->Remove();
1709	else
1710		srcEntry->Unset();
1711}
1712
1713
1714status_t
1715RecursiveMove(BEntry* entry, BDirectory* destDir,
1716	CopyLoopControl* loopControl)
1717{
1718	const char* name = entry->Name();
1719
1720	if (destDir->Contains(name)) {
1721		BPath path (destDir, name);
1722		BDirectory subDir (path.Path());
1723		entry_ref ref;
1724		entry->GetRef(&ref);
1725		BDirectory source(&ref);
1726		if (source.InitCheck() == B_OK) {
1727			source.Rewind();
1728			BEntry current;
1729			while (source.GetNextEntry(&current) == B_OK) {
1730				if (current.IsDirectory()) {
1731					RecursiveMove(&current, &subDir, loopControl);
1732					current.Remove();
1733				} else {
1734					name = current.Name();
1735					if (loopControl->OverwriteOnConflict(&current, name,
1736							&subDir, true, false)
1737								!= TrackerCopyLoopControl::kSkip) {
1738						MoveError::FailOnError(current.MoveTo(&subDir,
1739							NULL, true));
1740					}
1741				}
1742			}
1743		}
1744		entry->Remove();
1745	} else
1746		MoveError::FailOnError(entry->MoveTo(destDir));
1747
1748	return B_OK;
1749}
1750
1751status_t
1752MoveItem(BEntry* entry, BDirectory* destDir, BPoint* loc, uint32 moveMode,
1753	const char* newName, Undo &undo, CopyLoopControl* loopControl)
1754{
1755	entry_ref ref;
1756	try {
1757		node_ref destNode;
1758		StatStruct statbuf;
1759		MoveError::FailOnError(entry->GetStat(&statbuf));
1760		MoveError::FailOnError(entry->GetRef(&ref));
1761		MoveError::FailOnError(destDir->GetNodeRef(&destNode));
1762
1763		if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) {
1764			PoseInfo poseInfo;
1765			char name[B_FILE_NAME_LENGTH];
1766			strlcpy(name, ref.name, sizeof(name));
1767
1768			BSymLink link;
1769			BString suffix(" ");
1770			suffix << B_TRANSLATE_COMMENT("link", "filename link"),
1771			FSMakeOriginalName(name, destDir, suffix.String());
1772			undo.UpdateEntry(entry, name);
1773
1774			BPath path;
1775			entry->GetPath(&path);
1776			if (loc && loc != (BPoint*)-1) {
1777				poseInfo.fInvisible = false;
1778				poseInfo.fInitedDirectory = destNode.node;
1779				poseInfo.fLocation = *loc;
1780			}
1781
1782			status_t err = B_ERROR;
1783
1784			if (moveMode == kCreateRelativeLink) {
1785				if (statbuf.st_dev == destNode.device) {
1786					// relative link only works on the same device
1787					char oldwd[B_PATH_NAME_LENGTH];
1788					getcwd(oldwd, B_PATH_NAME_LENGTH);
1789
1790					BEntry destEntry;
1791					destDir -> GetEntry(&destEntry);
1792					BPath destPath;
1793					destEntry.GetPath(&destPath);
1794
1795					chdir(destPath.Path());
1796						// change working dir to target dir
1797
1798					BString destString(destPath.Path());
1799					destString.Append("/");
1800
1801					BString srcString(path.Path());
1802					srcString.RemoveLast(path.Leaf());
1803
1804					// find index while paths are the same
1805
1806					const char* src = srcString.String();
1807					const char* dest = destString.String();
1808					const char* lastFolderSrc = src;
1809					const char* lastFolderDest = dest;
1810
1811					while (*src && *dest && *src == *dest) {
1812						++src;
1813						if (*dest++ == '/') {
1814							lastFolderSrc = src;
1815							lastFolderDest = dest;
1816						}
1817					}
1818					src = lastFolderSrc;
1819					dest = lastFolderDest;
1820
1821					BString source;
1822					if (*dest == '\0' && *src != '\0') {
1823						// source is deeper in the same tree than the target
1824						source.Append(src);
1825					} else if (*dest != '\0') {
1826						// target is deeper in the same tree than the source
1827						while (*dest) {
1828							if (*dest == '/')
1829								source.Prepend("../");
1830							++dest;
1831						}
1832						source.Append(src);
1833					}
1834
1835					// else source and target are in the same dir
1836
1837					source.Append(path.Leaf());
1838					err = destDir->CreateSymLink(name, source.String(),
1839						&link);
1840
1841					chdir(oldwd);
1842						// change working dir back to original
1843				} else
1844					moveMode = kCreateLink;
1845						// fall back to absolute link mode
1846			}
1847
1848			if (moveMode == kCreateLink)
1849				err = destDir->CreateSymLink(name, path.Path(), &link);
1850
1851			if (err == B_UNSUPPORTED) {
1852				throw FailWithAlert(err,
1853					B_TRANSLATE("The target disk does not support "
1854					"creating links."), NULL);
1855			}
1856
1857			FailWithAlert::FailOnError(err,
1858				B_TRANSLATE("Error creating link to \"%name\"."),
1859				ref.name);
1860
1861			if (loc && loc != (BPoint*)-1) {
1862				link.WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
1863					sizeof(PoseInfo));
1864			}
1865
1866			BNodeInfo nodeInfo(&link);
1867			nodeInfo.SetType(B_LINK_MIMETYPE);
1868			return B_OK;
1869		}
1870
1871		// if move is on same volume don't copy
1872		if (statbuf.st_dev == destNode.device && moveMode != kCopySelectionTo
1873			&& moveMode != kDuplicateSelection) {
1874
1875			// for "Move" the size for status is always 1 - since file
1876			// size is irrelevant when simply moving to a new folder
1877			loopControl->UpdateStatus(ref.name, ref, 1);
1878			if (entry->IsDirectory())
1879				return RecursiveMove(entry, destDir, loopControl);
1880
1881			MoveError::FailOnError(entry->MoveTo(destDir, newName));
1882		} else {
1883			bool makeOriginalName = (moveMode == kDuplicateSelection);
1884			if (S_ISDIR(statbuf.st_mode)) {
1885				CopyFolder(entry, destDir, loopControl, loc, makeOriginalName,
1886					undo, moveMode == kMoveSelectionTo);
1887			} else {
1888				CopyFile(entry, &statbuf, destDir, loopControl, loc,
1889					makeOriginalName, undo);
1890				if (moveMode == kMoveSelectionTo)
1891					entry->Remove();
1892			}
1893		}
1894	} catch (status_t error) {
1895		// no alert, was already taken care of before
1896		return error;
1897	} catch (MoveError& error) {
1898		BString errorString(B_TRANSLATE("Error moving \"%name\""));
1899		errorString.ReplaceFirst("%name", ref.name);
1900		BAlert* alert = new BAlert("", errorString.String(), B_TRANSLATE("OK"),
1901			0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1902		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1903		alert->Go();
1904		return error.fError;
1905	} catch (FailWithAlert& error) {
1906		BString buffer(error.fString);
1907		if (error.fName != NULL)
1908			buffer.ReplaceFirst("%name", error.fName);
1909		else
1910			buffer << error.fString;
1911
1912		BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("OK"),
1913			0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1914		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1915		alert->Go();
1916
1917		return error.fError;
1918	}
1919
1920	return B_OK;
1921}
1922
1923
1924void
1925FSDuplicate(BObjectList<entry_ref>* srcList, BList* pointList)
1926{
1927	LaunchInNewThread("DupTask", B_NORMAL_PRIORITY, MoveTask, srcList,
1928		(BEntry*)NULL, pointList, kDuplicateSelection);
1929}
1930
1931
1932#if 0
1933status_t
1934FSCopyFolder(BEntry* srcEntry, BDirectory* destDir,
1935	CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName)
1936{
1937	try
1938		CopyFolder(srcEntry, destDir, loopControl, loc, makeOriginalName);
1939	catch (status_t error) {
1940		return error;
1941
1942	return B_OK;
1943}
1944#endif
1945
1946
1947status_t
1948FSCopyAttributesAndStats(BNode* srcNode, BNode* destNode, bool copyTimes)
1949{
1950	char* buffer = new char[1024];
1951
1952	// copy the attributes
1953	srcNode->RewindAttrs();
1954	char name[256];
1955	while (srcNode->GetNextAttrName(name) == B_OK) {
1956		attr_info info;
1957		if (srcNode->GetAttrInfo(name, &info) != B_OK)
1958			continue;
1959
1960		attr_info dest_info;
1961		if (destNode->GetAttrInfo(name, &dest_info) == B_OK)
1962			continue;
1963
1964		ssize_t bytes;
1965		ssize_t numToRead = (ssize_t)info.size;
1966		for (off_t offset = 0; numToRead > 0; offset += bytes) {
1967			size_t chunkSize = (size_t)numToRead;
1968			if (chunkSize > 1024)
1969				chunkSize = 1024;
1970
1971			bytes = srcNode->ReadAttr(name, info.type, offset, buffer,
1972				chunkSize);
1973
1974			if (bytes <= 0)
1975				break;
1976
1977			destNode->WriteAttr(name, info.type, offset, buffer,
1978				(size_t)bytes);
1979
1980			numToRead -= bytes;
1981		}
1982	}
1983	delete[] buffer;
1984
1985	// copy the file stats
1986	struct stat srcStat;
1987	srcNode->GetStat(&srcStat);
1988	destNode->SetPermissions(srcStat.st_mode);
1989	destNode->SetOwner(srcStat.st_uid);
1990	destNode->SetGroup(srcStat.st_gid);
1991	if (copyTimes) {
1992		destNode->SetModificationTime(srcStat.st_mtime);
1993		destNode->SetCreationTime(srcStat.st_crtime);
1994	}
1995
1996	return B_OK;
1997}
1998
1999
2000#if 0
2001status_t
2002FSCopyFile(BEntry* srcFile, StatStruct* srcStat, BDirectory* destDir,
2003	CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName)
2004{
2005	try {
2006		CopyFile(srcFile, srcStat, destDir, loopControl, loc,
2007			makeOriginalName);
2008	} catch (status_t error) {
2009		return error;
2010	}
2011
2012	return B_OK;
2013}
2014#endif
2015
2016
2017static status_t
2018MoveEntryToTrash(BEntry* entry, BPoint* loc, Undo &undo)
2019{
2020	BDirectory trash_dir;
2021	entry_ref ref;
2022	status_t result = entry->GetRef(&ref);
2023	if (result != B_OK)
2024		return result;
2025
2026	node_ref nodeRef;
2027	result = entry->GetNodeRef(&nodeRef);
2028	if (result != B_OK)
2029		return result;
2030
2031	StatStruct statbuf;
2032	result = entry->GetStat(&statbuf);
2033	if (entry->GetStat(&statbuf) != B_OK)
2034		return result;
2035
2036	// if it's a directory close the window and any child dir windows
2037	if (S_ISDIR(statbuf.st_mode)) {
2038		BDirectory dir(entry);
2039
2040		// if it's a volume, try to unmount
2041		if (dir.IsRootDirectory()) {
2042			BVolume volume(nodeRef.device);
2043			BVolume boot;
2044
2045			BVolumeRoster().GetBootVolume(&boot);
2046			if (volume == boot) {
2047				char name[B_FILE_NAME_LENGTH];
2048				volume.GetName(name);
2049				BString buffer(
2050					B_TRANSLATE("Cannot unmount the boot volume \"%name\"."));
2051				buffer.ReplaceFirst("%name", name);
2052				BAlert* alert = new BAlert("", buffer.String(),
2053					B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
2054					B_WARNING_ALERT);
2055				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2056				alert->Go();
2057			} else {
2058				BMessage message(kUnmountVolume);
2059				message.AddInt32("device_id", volume.Device());
2060				be_app->PostMessage(&message);
2061			}
2062			return B_OK;
2063		}
2064
2065		// get trash directory on same volume as item being moved
2066		result = FSGetTrashDir(&trash_dir, nodeRef.device);
2067		if (result != B_OK)
2068			return result;
2069
2070		// check hierarchy before moving
2071		BEntry trashEntry;
2072		trash_dir.GetEntry(&trashEntry);
2073
2074		if (dir == trash_dir || dir.Contains(&trashEntry)) {
2075			BAlert* alert = new BAlert("",
2076				B_TRANSLATE("You cannot put the selected item(s) "
2077					"into the trash."),
2078				B_TRANSLATE("OK"),
2079				0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2080			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2081			alert->Go();
2082
2083			// return no error so we don't get two dialogs
2084			return B_OK;
2085		}
2086
2087		BMessage message(kCloseWindowAndChildren);
2088
2089		node_ref parentNode;
2090		parentNode.device = statbuf.st_dev;
2091		parentNode.node = statbuf.st_ino;
2092		message.AddData("node_ref", B_RAW_TYPE, &parentNode, sizeof(node_ref));
2093		be_app->PostMessage(&message);
2094	} else {
2095		// get trash directory on same volume as item being moved
2096		result = FSGetTrashDir(&trash_dir, nodeRef.device);
2097		if (result != B_OK)
2098			return result;
2099	}
2100
2101	// make sure name doesn't conflict with anything in trash already
2102	char name[B_FILE_NAME_LENGTH];
2103	strlcpy(name, ref.name, sizeof(name));
2104	if (trash_dir.Contains(name)) {
2105		BString suffix(" ");
2106		suffix << B_TRANSLATE_COMMENT("copy", "filename copy"),
2107		FSMakeOriginalName(name, &trash_dir, suffix.String());
2108		undo.UpdateEntry(entry, name);
2109	}
2110
2111	BNode* src_node = 0;
2112	if (loc && loc != (BPoint*)-1
2113		&& (src_node = GetWritableNode(entry, &statbuf)) != 0) {
2114		trash_dir.GetStat(&statbuf);
2115		PoseInfo poseInfo;
2116		poseInfo.fInvisible = false;
2117		poseInfo.fInitedDirectory = statbuf.st_ino;
2118		poseInfo.fLocation = *loc;
2119		src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
2120			sizeof(poseInfo));
2121		delete src_node;
2122	}
2123
2124	BNode node(entry);
2125	BPath path;
2126	// Get path of entry before it's moved to the trash
2127	// and write it to the file as an attribute
2128	if (node.InitCheck() == B_OK && entry->GetPath(&path) == B_OK) {
2129		BString originalPath(path.Path());
2130		node.WriteAttrString(kAttrOriginalPath, &originalPath);
2131	}
2132
2133	TrackerCopyLoopControl loopControl;
2134	MoveItem(entry, &trash_dir, loc, kMoveSelectionTo, name, undo,
2135		&loopControl);
2136	return B_OK;
2137}
2138
2139
2140ConflictCheckResult
2141PreFlightNameCheck(BObjectList<entry_ref>* srcList, const BDirectory* destDir,
2142	int32* collisionCount, uint32 moveMode)
2143{
2144	// count the number of name collisions in dest folder
2145	*collisionCount = 0;
2146
2147	int32 count = srcList->CountItems();
2148	for (int32 i = 0; i < count; i++) {
2149		entry_ref* srcRef = srcList->ItemAt(i);
2150		BEntry entry(srcRef);
2151		BDirectory parent;
2152		entry.GetParent(&parent);
2153
2154		if (parent != *destDir && destDir->Contains(srcRef->name))
2155			(*collisionCount)++;
2156	}
2157
2158	// prompt user only if there is more than one collision, otherwise the
2159	// single collision case will be handled as a "Prompt" case by CheckName
2160	if (*collisionCount > 1) {
2161		const char* verb = (moveMode == kMoveSelectionTo)
2162			? B_TRANSLATE("moving") : B_TRANSLATE("copying");
2163		BString replaceMsg(B_TRANSLATE_NOCOLLECT(kReplaceManyStr));
2164		replaceMsg.ReplaceAll("%verb", verb);
2165
2166		BAlert* alert = new BAlert();
2167		alert->SetText(replaceMsg.String());
2168		alert->AddButton(B_TRANSLATE("Cancel"));
2169		alert->AddButton(B_TRANSLATE("Prompt"));
2170		alert->AddButton(B_TRANSLATE("Skip all"));
2171		alert->AddButton(B_TRANSLATE("Replace all"));
2172		alert->SetShortcut(0, B_ESCAPE);
2173		switch (alert->Go()) {
2174			case 0:
2175				return kCanceled;
2176
2177			case 1:
2178				// user selected "Prompt"
2179				return kPrompt;
2180
2181			case 2:
2182				// user selected "Skip all"
2183				return kSkipAll;
2184
2185			case 3:
2186				// user selected "Replace all"
2187				return kReplaceAll;
2188		}
2189	}
2190
2191	return kNoConflicts;
2192}
2193
2194
2195void
2196FileStatToString(StatStruct* stat, char* buffer, int32 length)
2197{
2198	tm timeData;
2199	localtime_r(&stat->st_mtime, &timeData);
2200
2201	BString size;
2202	static BStringFormat format(
2203		B_TRANSLATE("{0, plural, one{# byte} other{# bytes}}"));
2204	format.Format(size, stat->st_size);
2205	uint32 pos = snprintf(buffer, length, "\n\t(%s ", size.String());
2206
2207	strftime(buffer + pos, length - pos, "%b %d %Y, %I:%M:%S %p)", &timeData);
2208}
2209
2210
2211status_t
2212CheckName(uint32 moveMode, const BEntry* sourceEntry,
2213	const BDirectory* destDir, bool multipleCollisions,
2214	ConflictCheckResult& conflictResolution)
2215{
2216	if (moveMode == kDuplicateSelection) {
2217		// when duplicating, we will never have a conflict
2218		return B_OK;
2219	}
2220
2221	// see if item already exists in destination dir
2222	const char* name = sourceEntry->Name();
2223	bool sourceIsDirectory = sourceEntry->IsDirectory();
2224
2225	BDirectory srcDirectory;
2226	if (sourceIsDirectory) {
2227		srcDirectory.SetTo(sourceEntry);
2228		BEntry destEntry;
2229		destDir->GetEntry(&destEntry);
2230
2231		if (moveMode != kCreateLink && moveMode != kCreateRelativeLink
2232			&& (srcDirectory == *destDir
2233				|| srcDirectory.Contains(&destEntry))) {
2234			BAlert* alert = new BAlert("",
2235				B_TRANSLATE("You can't move a folder into itself "
2236				"or any of its own sub-folders."), B_TRANSLATE("OK"),
2237				0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2238			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2239			alert->Go();
2240			return B_ERROR;
2241		}
2242	}
2243
2244	if (FSIsTrashDir(sourceEntry) && moveMode != kCreateLink
2245		&& moveMode != kCreateRelativeLink) {
2246		BAlert* alert = new BAlert("",
2247			B_TRANSLATE("You can't move or copy the trash."),
2248			B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL,
2249			B_WARNING_ALERT);
2250		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2251		alert->Go();
2252		return B_ERROR;
2253	}
2254
2255	BEntry entry;
2256	if (destDir->FindEntry(name, &entry) != B_OK) {
2257		// no conflict, return
2258		return B_OK;
2259	}
2260
2261	if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) {
2262		// if we are creating link in the same directory, the conflict will
2263		// be handled later by giving the link a unique name
2264		sourceEntry->GetParent(&srcDirectory);
2265
2266		if (srcDirectory == *destDir)
2267			return B_OK;
2268	}
2269
2270	bool destIsDir = entry.IsDirectory();
2271	// be sure not to replace the parent directory of the item being moved
2272	if (destIsDir) {
2273		BDirectory targetDir(&entry);
2274		if (targetDir.Contains(sourceEntry)) {
2275			BAlert* alert = new BAlert("",
2276				B_TRANSLATE("You can't replace a folder "
2277				"with one of its sub-folders."),
2278				B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2279			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2280			alert->Go();
2281			return B_ERROR;
2282		}
2283	}
2284
2285	// ensure that the user isn't trying to replace a file with folder
2286	// or vice-versa
2287	if (moveMode != kCreateLink
2288		&& moveMode != kCreateRelativeLink
2289		&& destIsDir != sourceIsDirectory) {
2290		BAlert* alert = new BAlert("", sourceIsDirectory
2291			? B_TRANSLATE("You cannot replace a file with a folder or a "
2292				"symbolic link.")
2293			: B_TRANSLATE("You cannot replace a folder or a symbolic link "
2294				"with a file."), B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL,
2295			B_WARNING_ALERT);
2296		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2297		alert->Go();
2298		return B_ERROR;
2299	}
2300
2301	if (conflictResolution == kSkipAll)
2302		return B_ERROR;
2303
2304	if (conflictResolution != kReplaceAll) {
2305		// prompt user to determine whether to replace or not
2306		BString replaceMsg;
2307
2308		if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) {
2309			replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kSymLinkReplaceStr));
2310			replaceMsg.ReplaceFirst("%name", name);
2311		} else if (sourceEntry->IsDirectory()) {
2312			replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kDirectoryReplaceStr));
2313			replaceMsg.ReplaceFirst("%name", name);
2314			replaceMsg.ReplaceFirst("%verb",
2315				moveMode == kMoveSelectionTo
2316				? B_TRANSLATE("moving")
2317				: B_TRANSLATE("copying"));
2318		} else {
2319			char sourceBuffer[96], destBuffer[96];
2320			StatStruct statBuffer;
2321
2322			if (!sourceEntry->IsDirectory()
2323				&& sourceEntry->GetStat(&statBuffer) == B_OK) {
2324				FileStatToString(&statBuffer, sourceBuffer, 96);
2325			} else
2326				sourceBuffer[0] = '\0';
2327
2328			if (!entry.IsDirectory() && entry.GetStat(&statBuffer) == B_OK)
2329				FileStatToString(&statBuffer, destBuffer, 96);
2330			else
2331				destBuffer[0] = '\0';
2332
2333			replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kReplaceStr));
2334			replaceMsg.ReplaceAll("%name", name);
2335			replaceMsg.ReplaceFirst("%dest", destBuffer);
2336			replaceMsg.ReplaceFirst("%src", sourceBuffer);
2337			replaceMsg.ReplaceFirst("%movemode", moveMode == kMoveSelectionTo
2338				? B_TRANSLATE("moving") : B_TRANSLATE("copying"));
2339		}
2340
2341		// special case single collision (don't need Replace All shortcut)
2342		BAlert* alert;
2343		if (multipleCollisions || sourceIsDirectory) {
2344			alert = new BAlert();
2345			alert->SetText(replaceMsg.String());
2346			alert->AddButton(B_TRANSLATE("Skip"));
2347			alert->AddButton(B_TRANSLATE("Skip all"));
2348			alert->AddButton(B_TRANSLATE("Replace"));
2349			alert->AddButton(B_TRANSLATE("Replace all"));
2350			switch (alert->Go()) {
2351				case 0:
2352					conflictResolution = kCanceled;
2353					return B_ERROR;
2354				case 1:
2355					conflictResolution = kSkipAll;
2356					return B_ERROR;
2357				case 2:
2358					conflictResolution = kReplace;
2359					break;
2360				case 3:
2361					conflictResolution = kReplaceAll;
2362					break;
2363			}
2364		} else {
2365			alert = new BAlert("", replaceMsg.String(),
2366				B_TRANSLATE("Cancel"), B_TRANSLATE("Replace"));
2367			alert->SetShortcut(0, B_ESCAPE);
2368			switch (alert->Go()) {
2369				case 0:
2370					conflictResolution = kCanceled;
2371					return B_ERROR;
2372				case 1:
2373					conflictResolution = kReplace;
2374					break;
2375			}
2376		}
2377	}
2378
2379	// delete destination item
2380	if (destIsDir)
2381		return B_OK;
2382
2383	status_t status = entry.Remove();
2384	if (status != B_OK) {
2385		BString error(B_TRANSLATE("There was a problem trying to replace "
2386			"\"%name\". The item might be open or busy."));
2387		error.ReplaceFirst("%name", name);
2388		BAlert* alert = new BAlert("", error.String(),
2389			B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2390		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2391		alert->Go();
2392	}
2393
2394	return status;
2395}
2396
2397
2398status_t
2399FSDeleteFolder(BEntry* dirEntry, CopyLoopControl* loopControl,
2400	bool updateStatus, bool deleteTopDir, bool upateFileNameInStatus)
2401{
2402	BDirectory dir(dirEntry);
2403
2404	// loop through everything in folder and delete it, skipping trouble files
2405	BEntry entry;
2406	while (dir.GetNextEntry(&entry) == B_OK) {
2407		entry_ref ref;
2408		entry.GetRef(&ref);
2409
2410		if (loopControl->CheckUserCanceled())
2411			return kTrashCanceled;
2412
2413		status_t status;
2414
2415		if (entry.IsDirectory())
2416			status = FSDeleteFolder(&entry, loopControl, updateStatus, true,
2417				upateFileNameInStatus);
2418		else {
2419			status = entry.Remove();
2420			if (updateStatus) {
2421				loopControl->UpdateStatus(upateFileNameInStatus ? ref.name
2422					: "", ref, 1, true);
2423			}
2424		}
2425
2426		if (status == kTrashCanceled)
2427			return kTrashCanceled;
2428
2429		if (status != B_OK) {
2430			loopControl->FileError(B_TRANSLATE_NOCOLLECT(
2431					kFileDeleteErrorString), ref.name, status, false);
2432		}
2433	}
2434
2435	if (loopControl->CheckUserCanceled())
2436		return kTrashCanceled;
2437
2438	entry_ref ref;
2439	dirEntry->GetRef(&ref);
2440
2441	if (updateStatus && deleteTopDir)
2442		loopControl->UpdateStatus(NULL, ref, 1);
2443
2444	if (deleteTopDir)
2445		return dirEntry->Remove();
2446
2447	return B_OK;
2448}
2449
2450
2451void
2452FSMakeOriginalName(BString &string, const BDirectory* destDir,
2453	const char* suffix)
2454{
2455	if (!destDir->Contains(string.String()))
2456		return;
2457
2458	FSMakeOriginalName(string.LockBuffer(B_FILE_NAME_LENGTH),
2459		const_cast<BDirectory*>(destDir), suffix ? suffix : " copy");
2460	string.UnlockBuffer();
2461}
2462
2463
2464void
2465FSMakeOriginalName(char* name, BDirectory* destDir, const char* suffix)
2466{
2467	char		root[B_FILE_NAME_LENGTH];
2468	char		copybase[B_FILE_NAME_LENGTH];
2469	char		tempName[B_FILE_NAME_LENGTH + 11];
2470	int32		fnum;
2471
2472	// is this name already original?
2473	if (!destDir->Contains(name))
2474		return;
2475
2476	// Determine if we're copying a 'copy'. This algorithm isn't perfect.
2477	// If you're copying a file whose REAL name ends with 'copy' then
2478	// this method will return "<filename> 1", not "<filename> copy"
2479
2480	// However, it will correctly handle file that contain 'copy'
2481	// elsewhere in their name.
2482
2483	bool copycopy = false;		// are we copying a copy?
2484	int32 len = (int32)strlen(name);
2485	char* p = name + len - 1;	// get pointer to end os name
2486
2487	// eat up optional numbers (if were copying "<filename> copy 34")
2488	while ((p > name) && isdigit(*p))
2489		p--;
2490
2491	// eat up optional spaces
2492	while ((p > name) && isspace(*p))
2493		p--;
2494
2495	// now look for the phrase " copy"
2496	if (p > name) {
2497		// p points to the last char of the word. For example, 'y' in 'copy'
2498
2499		if ((p - 4 > name) && (strncmp(p - 4, suffix, 5) == 0)) {
2500			// we found 'copy' in the right place.
2501			// so truncate after 'copy'
2502			*(p + 1) = '\0';
2503			copycopy = true;
2504
2505			// save the 'root' name of the file, for possible later use.
2506			// that is copy everything but trailing " copy". Need to
2507			// NULL terminate after copy
2508			strncpy(root, name, (uint32)((p - name) - 4));
2509			root[(p - name) - 4] = '\0';
2510		}
2511	}
2512
2513	if (!copycopy) {
2514		// The name can't be longer than B_FILE_NAME_LENGTH.
2515		// The algoritm adds " copy XX" to the name. That's 8 characters.
2516		// B_FILE_NAME_LENGTH already accounts for NULL termination so we
2517		// don't need to save an extra char at the end.
2518		if (strlen(name) > B_FILE_NAME_LENGTH - 8) {
2519			// name is too long - truncate it!
2520			name[B_FILE_NAME_LENGTH - 8] = '\0';
2521		}
2522
2523		strlcpy(root, name, sizeof(root));
2524			// save root name
2525		strlcat(name, suffix, B_FILE_NAME_LENGTH);
2526	}
2527
2528	strlcpy(copybase, name, sizeof(copybase));
2529
2530	// if name already exists then add a number
2531	fnum = 1;
2532	strlcpy(tempName, name, sizeof(tempName));
2533	while (destDir->Contains(tempName)) {
2534		snprintf(tempName, sizeof(tempName), "%s %" B_PRId32, copybase,
2535			++fnum);
2536
2537		if (strlen(tempName) > (B_FILE_NAME_LENGTH - 1)) {
2538			// The name has grown too long. Maybe we just went from
2539			// "<filename> copy 9" to "<filename> copy 10" and that extra
2540			// character was too much. The solution is to further
2541			// truncate the 'root' name and continue.
2542			// ??? should we reset fnum or not ???
2543			root[strlen(root) - 1] = '\0';
2544			snprintf(tempName, sizeof(tempName), "%s%s %" B_PRId32, root,
2545				suffix, fnum);
2546		}
2547	}
2548
2549	strlcpy(name, tempName, B_FILE_NAME_LENGTH);
2550}
2551
2552
2553status_t
2554FSRecursiveCalcSize(BInfoWindow* window, CopyLoopControl* loopControl,
2555	BDirectory* dir, off_t* _runningSize, int32* _fileCount, int32* _dirCount)
2556{
2557	dir->Rewind();
2558	BEntry entry;
2559	while (dir->GetNextEntry(&entry) == B_OK) {
2560		// be sure window hasn't closed
2561		if (window && window->StopCalc())
2562			return B_OK;
2563
2564		if (loopControl->CheckUserCanceled())
2565			return kUserCanceled;
2566
2567		StatStruct statbuf;
2568		status_t status = entry.GetStat(&statbuf);
2569		if (status != B_OK)
2570			return status;
2571
2572		(*_runningSize) += statbuf.st_blocks * 512;
2573
2574		if (S_ISDIR(statbuf.st_mode)) {
2575			BDirectory subdir(&entry);
2576			(*_dirCount)++;
2577			status = FSRecursiveCalcSize(window, loopControl, &subdir,
2578				_runningSize, _fileCount, _dirCount);
2579			if (status != B_OK)
2580				return status;
2581		} else
2582			(*_fileCount)++;
2583	}
2584	return B_OK;
2585}
2586
2587
2588status_t
2589CalcItemsAndSize(CopyLoopControl* loopControl,
2590	BObjectList<entry_ref>* refList, ssize_t blockSize, int32* totalCount,
2591	off_t* totalSize)
2592{
2593	int32 fileCount = 0;
2594	int32 dirCount = 0;
2595
2596	// check block size for sanity
2597	if (blockSize < 0) {
2598		// This would point at an error to retrieve the block size from
2599		// the target volume. The code below cannot be used, it is only
2600		// meant to get the block size when item operations happen on
2601		// the source volume.
2602		blockSize = 2048;
2603	} else if (blockSize < 1024) {
2604		blockSize = 1024;
2605		if (entry_ref* ref = refList->ItemAt(0)) {
2606			// TODO: This assumes all entries in the list share the same
2607			// volume...
2608			BVolume volume(ref->device);
2609			if (volume.InitCheck() == B_OK)
2610				blockSize = volume.BlockSize();
2611		}
2612	}
2613	// File systems like ReiserFS may advertize a large block size, but
2614	// stuff is still packed into blocks, so clamp maximum block size.
2615	if (blockSize > 8192)
2616		blockSize = 8192;
2617
2618	int32 num_items = refList->CountItems();
2619	for (int32 i = 0; i < num_items; i++) {
2620		entry_ref* ref = refList->ItemAt(i);
2621		BEntry entry(ref);
2622		StatStruct statbuf;
2623		entry.GetStat(&statbuf);
2624
2625		if (loopControl->CheckUserCanceled())
2626			return kUserCanceled;
2627
2628		if (S_ISDIR(statbuf.st_mode)) {
2629			BDirectory dir(&entry);
2630			dirCount++;
2631			(*totalSize) += blockSize;
2632			status_t result = FSRecursiveCalcSize(NULL, loopControl, &dir,
2633				totalSize, &fileCount, &dirCount);
2634			if (result != B_OK)
2635				return result;
2636		} else {
2637			fileCount++;
2638			(*totalSize) += statbuf.st_size + blockSize;
2639		}
2640	}
2641
2642	*totalCount += (fileCount + dirCount);
2643	return B_OK;
2644}
2645
2646
2647status_t
2648FSGetTrashDir(BDirectory* trashDir, dev_t dev)
2649{
2650	if (trashDir == NULL)
2651		return B_BAD_VALUE;
2652
2653	BVolume volume(dev);
2654	status_t result = volume.InitCheck();
2655	if (result != B_OK)
2656		return result;
2657
2658	BPath path;
2659	result = find_directory(B_TRASH_DIRECTORY, &path, false, &volume);
2660	if (result != B_OK)
2661		return result;
2662
2663	result = trashDir->SetTo(path.Path());
2664	if (result != B_OK) {
2665		// Trash directory does not exist yet, create it.
2666		result = create_directory(path.Path(), 0755);
2667		if (result != B_OK)
2668			return result;
2669
2670		result = trashDir->SetTo(path.Path());
2671		if (result != B_OK)
2672			return result;
2673
2674		// make Trash directory invisible
2675		StatStruct sbuf;
2676		trashDir->GetStat(&sbuf);
2677
2678		PoseInfo poseInfo;
2679		poseInfo.fInvisible = true;
2680		poseInfo.fInitedDirectory = sbuf.st_ino;
2681		trashDir->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
2682			sizeof(PoseInfo));
2683	}
2684
2685	// set Trash icons (if they haven't already been set)
2686	attr_info attrInfo;
2687	size_t size;
2688	const void* data;
2689	if (trashDir->GetAttrInfo(kAttrLargeIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2690		data = GetTrackerResources()->LoadResource('ICON', R_TrashIcon, &size);
2691		if (data != NULL)
2692			trashDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
2693	}
2694
2695	if (trashDir->GetAttrInfo(kAttrMiniIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2696		data = GetTrackerResources()->LoadResource('MICN', R_TrashIcon, &size);
2697		if (data != NULL)
2698			trashDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
2699	}
2700
2701	if (trashDir->GetAttrInfo(kAttrIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2702		data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE,
2703			R_TrashIcon, &size);
2704		if (data != NULL)
2705			trashDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
2706	}
2707
2708	return B_OK;
2709}
2710
2711
2712status_t
2713FSGetDeskDir(BDirectory* deskDir)
2714{
2715	if (deskDir == NULL)
2716		return B_BAD_VALUE;
2717
2718	BPath path;
2719	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true);
2720	if (result != B_OK)
2721		return result;
2722
2723	result = deskDir->SetTo(path.Path());
2724	if (result != B_OK)
2725		return result;
2726
2727	// set Desktop icons (if they haven't already been set)
2728	attr_info attrInfo;
2729	size_t size;
2730	const void* data;
2731	if (deskDir->GetAttrInfo(kAttrLargeIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2732		data = GetTrackerResources()->LoadResource('ICON', R_DeskIcon, &size);
2733		if (data != NULL)
2734			deskDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
2735	}
2736
2737	if (deskDir->GetAttrInfo(kAttrMiniIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2738		data = GetTrackerResources()->LoadResource('MICN', R_DeskIcon, &size);
2739		if (data != NULL)
2740			deskDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
2741	}
2742
2743	if (deskDir->GetAttrInfo(kAttrIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2744		data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE,
2745			R_DeskIcon, &size);
2746		if (data != NULL)
2747			deskDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
2748	}
2749
2750	return B_OK;
2751}
2752
2753
2754status_t
2755FSGetBootDeskDir(BDirectory* deskDir)
2756{
2757	BVolume bootVolume;
2758	BVolumeRoster().GetBootVolume(&bootVolume);
2759	BPath path;
2760
2761	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true,
2762		&bootVolume);
2763	if (result != B_OK)
2764		return result;
2765
2766	return deskDir->SetTo(path.Path());
2767}
2768
2769
2770static bool
2771FSIsDirFlavor(const BEntry* entry, directory_which directoryType)
2772{
2773	StatStruct dir_stat;
2774	StatStruct entry_stat;
2775	BVolume volume;
2776	BPath path;
2777
2778	if (entry->GetStat(&entry_stat) != B_OK)
2779		return false;
2780
2781	if (volume.SetTo(entry_stat.st_dev) != B_OK)
2782		return false;
2783
2784	if (find_directory(directoryType, &path, false, &volume) != B_OK)
2785		return false;
2786
2787	stat(path.Path(), &dir_stat);
2788
2789	return dir_stat.st_ino == entry_stat.st_ino
2790		&& dir_stat.st_dev == entry_stat.st_dev;
2791}
2792
2793
2794bool
2795FSIsPrintersDir(const BEntry* entry)
2796{
2797	return FSIsDirFlavor(entry, B_USER_PRINTERS_DIRECTORY);
2798}
2799
2800
2801bool
2802FSIsTrashDir(const BEntry* entry)
2803{
2804	return FSIsDirFlavor(entry, B_TRASH_DIRECTORY);
2805}
2806
2807
2808bool
2809FSIsDeskDir(const BEntry* entry)
2810{
2811	BPath path;
2812	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true);
2813	if (result != B_OK)
2814		return false;
2815
2816	BEntry entryToCompare(path.Path());
2817	return entryToCompare == *entry;
2818}
2819
2820
2821bool
2822FSIsHomeDir(const BEntry* entry)
2823{
2824	return FSIsDirFlavor(entry, B_USER_DIRECTORY);
2825}
2826
2827
2828bool
2829FSIsRootDir(const BEntry* entry)
2830{
2831	BPath path;
2832	if (entry->InitCheck() != B_OK || entry->GetPath(&path) != B_OK)
2833		return false;
2834
2835	return strcmp(path.Path(), "/") == 0;
2836}
2837
2838
2839bool
2840DirectoryMatchesOrContains(const BEntry* entry, directory_which which)
2841{
2842	BPath path;
2843	if (find_directory(which, &path, false, NULL) != B_OK)
2844		return false;
2845
2846	BEntry dirEntry(path.Path());
2847	if (dirEntry.InitCheck() != B_OK)
2848		return false;
2849
2850	if (dirEntry == *entry)
2851		// root level match
2852		return true;
2853
2854	BDirectory dir(&dirEntry);
2855	return dir.Contains(entry);
2856}
2857
2858
2859bool
2860DirectoryMatchesOrContains(const BEntry* entry, const char* additionalPath,
2861	directory_which which)
2862{
2863	BPath path;
2864	if (find_directory(which, &path, false, NULL) != B_OK)
2865		return false;
2866
2867	path.Append(additionalPath);
2868	BEntry dirEntry(path.Path());
2869	if (dirEntry.InitCheck() != B_OK)
2870		return false;
2871
2872	if (dirEntry == *entry)
2873		// root level match
2874		return true;
2875
2876	BDirectory dir(&dirEntry);
2877	return dir.Contains(entry);
2878}
2879
2880
2881bool
2882DirectoryMatches(const BEntry* entry, directory_which which)
2883{
2884	BPath path;
2885	if (find_directory(which, &path, false, NULL) != B_OK)
2886		return false;
2887
2888	BEntry dirEntry(path.Path());
2889	if (dirEntry.InitCheck() != B_OK)
2890		return false;
2891
2892	return dirEntry == *entry;
2893}
2894
2895
2896bool
2897DirectoryMatches(const BEntry* entry, const char* additionalPath,
2898	directory_which which)
2899{
2900	BPath path;
2901	if (find_directory(which, &path, false, NULL) != B_OK)
2902		return false;
2903
2904	path.Append(additionalPath);
2905	BEntry dirEntry(path.Path());
2906	if (dirEntry.InitCheck() != B_OK)
2907		return false;
2908
2909	return dirEntry == *entry;
2910}
2911
2912
2913extern status_t
2914FSFindTrackerSettingsDir(BPath* path, bool autoCreate)
2915{
2916	status_t result = find_directory(B_USER_SETTINGS_DIRECTORY, path,
2917		autoCreate);
2918	if (result != B_OK)
2919		return result;
2920
2921	path->Append("Tracker");
2922
2923	return mkdir(path->Path(), 0777) ? B_OK : errno;
2924}
2925
2926
2927bool
2928FSInTrashDir(const entry_ref* ref)
2929{
2930	BEntry entry(ref);
2931	if (entry.InitCheck() != B_OK)
2932		return false;
2933
2934	BDirectory trashDir;
2935	if (FSGetTrashDir(&trashDir, ref->device) != B_OK)
2936		return false;
2937
2938	return trashDir.Contains(&entry);
2939}
2940
2941
2942void
2943FSEmptyTrash()
2944{
2945	if (find_thread("_tracker_empty_trash_") != B_OK) {
2946		resume_thread(spawn_thread(empty_trash, "_tracker_empty_trash_",
2947			B_NORMAL_PRIORITY, NULL));
2948	}
2949}
2950
2951
2952status_t
2953empty_trash(void*)
2954{
2955	// empty trash on all mounted volumes
2956	status_t status = B_OK;
2957
2958	TrackerCopyLoopControl loopControl(kTrashState);
2959
2960	// calculate the sum total of all items on all volumes in trash
2961	BObjectList<entry_ref> srcList;
2962	int32 totalCount = 0;
2963	off_t totalSize = 0;
2964
2965	BVolumeRoster volumeRoster;
2966	BVolume volume;
2967	while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2968		if (volume.IsReadOnly() || !volume.IsPersistent())
2969			continue;
2970
2971		BDirectory trashDirectory;
2972		if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
2973			continue;
2974
2975		BEntry entry;
2976		trashDirectory.GetEntry(&entry);
2977
2978		entry_ref ref;
2979		entry.GetRef(&ref);
2980		srcList.AddItem(&ref);
2981		status = CalcItemsAndSize(&loopControl, &srcList, volume.BlockSize(),
2982			&totalCount, &totalSize);
2983		if (status != B_OK)
2984			break;
2985
2986		srcList.MakeEmpty();
2987
2988		// don't count trash directory itself
2989		totalCount--;
2990	}
2991
2992	if (status == B_OK) {
2993		loopControl.Init(totalCount, totalCount);
2994
2995		volumeRoster.Rewind();
2996		while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2997			if (volume.IsReadOnly() || !volume.IsPersistent())
2998				continue;
2999
3000			BDirectory trashDirectory;
3001			if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
3002				continue;
3003
3004			BEntry entry;
3005			trashDirectory.GetEntry(&entry);
3006			status = FSDeleteFolder(&entry, &loopControl, true, false);
3007		}
3008	}
3009
3010	if (status != B_OK && status != kTrashCanceled && status != kUserCanceled) {
3011		BAlert* alert = new BAlert("", B_TRANSLATE("Error emptying Trash"),
3012			B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3013			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3014			alert->Go();
3015	}
3016
3017	return B_OK;
3018}
3019
3020
3021status_t
3022_DeleteTask(BObjectList<entry_ref>* list, bool confirm)
3023{
3024	if (confirm) {
3025		BAlert* alert = new BAlert("",
3026			B_TRANSLATE_NOCOLLECT(kDeleteConfirmationStr),
3027			B_TRANSLATE("Cancel"), B_TRANSLATE("Move to Trash"),
3028			B_TRANSLATE("Delete"),
3029			B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
3030
3031		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3032		alert->SetShortcut(0, B_ESCAPE);
3033		alert->SetShortcut(1, 'm');
3034		alert->SetShortcut(2, 'd');
3035
3036		switch (alert->Go()) {
3037			case 0:
3038				delete list;
3039				return B_CANCELED;
3040
3041			case 1:
3042			default:
3043				FSMoveToTrash(list, NULL, false);
3044				return B_OK;
3045
3046			case 2:
3047				break;
3048		}
3049	}
3050
3051	TrackerCopyLoopControl loopControl(kDeleteState);
3052
3053	// calculate the sum total of all items on all volumes in trash
3054	int32 totalItems = 0;
3055	int64 totalSize = 0;
3056
3057	status_t status = CalcItemsAndSize(&loopControl, list, 0, &totalItems,
3058		&totalSize);
3059	if (status == B_OK) {
3060		loopControl.Init(totalItems, totalItems);
3061
3062		int32 count = list->CountItems();
3063		for (int32 index = 0; index < count; index++) {
3064			entry_ref ref(*list->ItemAt(index));
3065			BEntry entry(&ref);
3066			loopControl.UpdateStatus(ref.name, ref, 1, true);
3067			if (entry.IsDirectory())
3068				status = FSDeleteFolder(&entry, &loopControl, true, true, true);
3069			else
3070				status = entry.Remove();
3071		}
3072
3073		if (status != kTrashCanceled && status != kUserCanceled
3074			&& status != B_OK) {
3075			BAlert* alert = new BAlert("", B_TRANSLATE("Error deleting items"),
3076				B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
3077				B_WARNING_ALERT);
3078			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3079			alert->Go();
3080		}
3081	}
3082
3083	delete list;
3084
3085	return B_OK;
3086}
3087
3088status_t
3089FSRecursiveCreateFolder(BPath path)
3090{
3091	BEntry entry(path.Path());
3092	if (entry.InitCheck() != B_OK) {
3093		BPath parentPath;
3094		status_t err = path.GetParent(&parentPath);
3095		if (err != B_OK)
3096			return err;
3097
3098		err = FSRecursiveCreateFolder(parentPath);
3099		if (err != B_OK)
3100			return err;
3101	}
3102
3103	entry.SetTo(path.Path());
3104	if (entry.Exists())
3105		return B_FILE_EXISTS;
3106
3107	BDirectory parent;
3108	entry.GetParent(&parent);
3109	parent.CreateDirectory(entry.Name(), NULL);
3110
3111	return B_OK;
3112}
3113
3114status_t
3115_RestoreTask(BObjectList<entry_ref>* list)
3116{
3117	TrackerCopyLoopControl loopControl(kRestoreFromTrashState);
3118
3119	// calculate the sum total of all items that will be restored
3120	int32 totalItems = 0;
3121	int64 totalSize = 0;
3122
3123	status_t err = CalcItemsAndSize(&loopControl, list, 0, &totalItems,
3124		&totalSize);
3125	if (err == B_OK) {
3126		loopControl.Init(totalItems, totalItems);
3127
3128		int32 count = list->CountItems();
3129		for (int32 index = 0; index < count; index++) {
3130			entry_ref ref(*list->ItemAt(index));
3131			BEntry entry(&ref);
3132			BPath originalPath;
3133
3134			loopControl.UpdateStatus(ref.name, ref, 1, true);
3135
3136			if (FSGetOriginalPath(&entry, &originalPath) != B_OK)
3137				continue;
3138
3139			BEntry originalEntry(originalPath.Path());
3140			BPath parentPath;
3141			err = originalPath.GetParent(&parentPath);
3142			if (err != B_OK)
3143				continue;
3144			BEntry parentEntry(parentPath.Path());
3145
3146			if (parentEntry.InitCheck() != B_OK || !parentEntry.Exists()) {
3147				if (FSRecursiveCreateFolder(parentPath) == B_OK) {
3148					originalEntry.SetTo(originalPath.Path());
3149					if (entry.InitCheck() != B_OK)
3150						continue;
3151				}
3152			}
3153
3154			if (!originalEntry.Exists()) {
3155				BDirectory dir(parentPath.Path());
3156				if (dir.InitCheck() == B_OK) {
3157					const char* leafName = originalEntry.Name();
3158					if (entry.MoveTo(&dir, leafName) == B_OK) {
3159						BNode node(&entry);
3160						if (node.InitCheck() == B_OK)
3161							node.RemoveAttr(kAttrOriginalPath);
3162					}
3163				}
3164			}
3165
3166			err = loopControl.CheckUserCanceled();
3167			if (err != B_OK)
3168				break;
3169		}
3170	}
3171
3172	delete list;
3173
3174	return err;
3175}
3176
3177void
3178FSCreateTrashDirs()
3179{
3180	BVolume volume;
3181	BVolumeRoster roster;
3182
3183	roster.Rewind();
3184	while (roster.GetNextVolume(&volume) == B_OK) {
3185		if (volume.IsReadOnly() || !volume.IsPersistent())
3186			continue;
3187
3188		BDirectory trashDir;
3189		FSGetTrashDir(&trashDir, volume.Device());
3190	}
3191}
3192
3193
3194status_t
3195FSCreateNewFolder(const entry_ref* ref)
3196{
3197	node_ref node;
3198	node.device = ref->device;
3199	node.node = ref->directory;
3200
3201	BDirectory dir(&node);
3202	status_t result = dir.InitCheck();
3203	if (result != B_OK)
3204		return result;
3205
3206	// ToDo: is that really necessary here?
3207	BString name(ref->name);
3208	FSMakeOriginalName(name, &dir, "-");
3209
3210	BDirectory newDir;
3211	result = dir.CreateDirectory(name.String(), &newDir);
3212	if (result != B_OK)
3213		return result;
3214
3215	BNodeInfo nodeInfo(&newDir);
3216	nodeInfo.SetType(B_DIR_MIMETYPE);
3217
3218	return result;
3219}
3220
3221
3222status_t
3223FSCreateNewFolderIn(const node_ref* dirNode, entry_ref* newRef,
3224	node_ref* newNode)
3225{
3226	BDirectory dir(dirNode);
3227	status_t result = dir.InitCheck();
3228	if (result == B_OK) {
3229		char name[B_FILE_NAME_LENGTH];
3230		strlcpy(name, B_TRANSLATE("New folder"), sizeof(name));
3231
3232		int32 fnum = 1;
3233		while (dir.Contains(name)) {
3234			// if base name already exists then add a number
3235			// TODO: move this logic to FSMakeOriginalName
3236			if (++fnum > 9) {
3237				snprintf(name, sizeof(name), B_TRANSLATE("New folder%ld"),
3238					fnum);
3239			} else {
3240				snprintf(name, sizeof(name), B_TRANSLATE("New folder %ld"),
3241					fnum);
3242			}
3243		}
3244
3245		BDirectory newDir;
3246		result = dir.CreateDirectory(name, &newDir);
3247		if (result == B_OK) {
3248			BEntry entry;
3249			newDir.GetEntry(&entry);
3250			entry.GetRef(newRef);
3251			entry.GetNodeRef(newNode);
3252
3253			BNodeInfo nodeInfo(&newDir);
3254			nodeInfo.SetType(B_DIR_MIMETYPE);
3255
3256			// add undo item
3257			NewFolderUndo undo(*newRef);
3258			return B_OK;
3259		}
3260	}
3261
3262	BAlert* alert = new BAlert("",
3263		B_TRANSLATE("Sorry, could not create a new folder."),
3264		B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3265	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3266	alert->Go();
3267
3268	return result;
3269}
3270
3271
3272ReadAttrResult
3273ReadAttr(const BNode* node, const char* hostAttrName,
3274	const char* foreignAttrName, type_code type, off_t offset, void* buffer,
3275	size_t length, void (*swapFunc)(void*), bool isForeign)
3276{
3277	if (!isForeign && node->ReadAttr(hostAttrName, type, offset, buffer,
3278			length) == (ssize_t)length) {
3279		return kReadAttrNativeOK;
3280	}
3281
3282	// PRINT(("trying %s\n", foreignAttrName));
3283	// try the other endianness
3284	if (node->ReadAttr(foreignAttrName, type, offset, buffer, length)
3285			!= (ssize_t)length) {
3286		return kReadAttrFailed;
3287	}
3288
3289	// PRINT(("got %s\n", foreignAttrName));
3290	if (!swapFunc)
3291		return kReadAttrForeignOK;
3292
3293	(swapFunc)(buffer);
3294		// run the endian swapper
3295
3296	return kReadAttrForeignOK;
3297}
3298
3299
3300ReadAttrResult
3301GetAttrInfo(const BNode* node, const char* hostAttrName,
3302	const char* foreignAttrName, type_code* type, size_t* size)
3303{
3304	attr_info info;
3305
3306	if (node->GetAttrInfo(hostAttrName, &info) == B_OK) {
3307		if (type)
3308			*type = info.type;
3309		if (size)
3310			*size = (size_t)info.size;
3311
3312		return kReadAttrNativeOK;
3313	}
3314
3315	if (node->GetAttrInfo(foreignAttrName, &info) == B_OK) {
3316		if (type)
3317			*type = info.type;
3318		if (size)
3319			*size = (size_t)info.size;
3320
3321		return kReadAttrForeignOK;
3322	}
3323	return kReadAttrFailed;
3324}
3325
3326
3327status_t
3328FSGetParentVirtualDirectoryAware(const BEntry& entry, entry_ref& _ref)
3329{
3330	node_ref nodeRef;
3331	if (entry.GetNodeRef(&nodeRef) == B_OK) {
3332		if (VirtualDirectoryManager* manager
3333				= VirtualDirectoryManager::Instance()) {
3334			AutoLocker<VirtualDirectoryManager> managerLocker(manager);
3335			if (manager->GetParentDirectoryDefinitionFile(nodeRef, _ref,
3336					nodeRef)) {
3337				return B_OK;
3338			}
3339		}
3340	}
3341
3342	status_t error;
3343	BDirectory parent;
3344	BEntry parentEntry;
3345	if ((error = entry.GetParent(&parent)) != B_OK
3346		|| (error = parent.GetEntry(&parentEntry)) != B_OK
3347		|| (error = parentEntry.GetRef(&_ref)) != B_OK) {
3348		return error;
3349	}
3350
3351	return B_OK;
3352}
3353
3354
3355status_t
3356FSGetParentVirtualDirectoryAware(const BEntry& entry, BEntry& _entry)
3357{
3358	node_ref nodeRef;
3359	if (entry.GetNodeRef(&nodeRef) == B_OK) {
3360		if (VirtualDirectoryManager* manager
3361				= VirtualDirectoryManager::Instance()) {
3362			AutoLocker<VirtualDirectoryManager> managerLocker(manager);
3363			entry_ref parentRef;
3364			if (manager->GetParentDirectoryDefinitionFile(nodeRef, parentRef,
3365					nodeRef)) {
3366				return _entry.SetTo(&parentRef);
3367			}
3368		}
3369	}
3370
3371	return entry.GetParent(&_entry);
3372}
3373
3374
3375status_t
3376FSGetParentVirtualDirectoryAware(const BEntry& entry, BNode& _node)
3377{
3378	entry_ref ref;
3379	status_t error = FSGetParentVirtualDirectoryAware(entry, ref);
3380	if (error == B_OK)
3381		error = _node.SetTo(&ref);
3382
3383	return error;
3384}
3385
3386
3387// launching code
3388
3389static status_t
3390TrackerOpenWith(const BMessage* refs)
3391{
3392	BMessage clone(*refs);
3393
3394	ASSERT(dynamic_cast<TTracker*>(be_app) != NULL);
3395	ASSERT(clone.what != 0);
3396
3397	clone.AddInt32("launchUsingSelector", 0);
3398	// runs the Open With window
3399	be_app->PostMessage(&clone);
3400
3401	return B_OK;
3402}
3403
3404
3405static void
3406AsynchLaunchBinder(void (*func)(const entry_ref*, const BMessage*, bool on),
3407	const entry_ref* appRef, const BMessage* refs, bool openWithOK)
3408{
3409	BMessage* task = new BMessage;
3410	task->AddPointer("function", (void*)func);
3411	task->AddMessage("refs", refs);
3412	task->AddBool("openWithOK", openWithOK);
3413	if (appRef != NULL)
3414		task->AddRef("appRef", appRef);
3415
3416	extern BLooper* gLaunchLooper;
3417	gLaunchLooper->PostMessage(task);
3418}
3419
3420
3421static bool
3422SniffIfGeneric(const entry_ref* ref)
3423{
3424	BNode node(ref);
3425	char type[B_MIME_TYPE_LENGTH];
3426	BNodeInfo info(&node);
3427	if (info.GetType(type) == B_OK
3428		&& strcasecmp(type, B_FILE_MIME_TYPE) != 0) {
3429		// already has a type and it's not octet stream
3430		return false;
3431	}
3432
3433	BPath path(ref);
3434	if (path.Path()) {
3435		// force a mimeset
3436		node.RemoveAttr(kAttrMIMEType);
3437		update_mime_info(path.Path(), 0, 1, 1);
3438	}
3439
3440	return true;
3441}
3442
3443
3444static void
3445SniffIfGeneric(const BMessage* refs)
3446{
3447	entry_ref ref;
3448	for (int32 index = 0; ; index++) {
3449		if (refs->FindRef("refs", index, &ref) != B_OK)
3450			break;
3451		SniffIfGeneric(&ref);
3452	}
3453}
3454
3455
3456static void
3457_TrackerLaunchAppWithDocuments(const entry_ref* appRef, const BMessage* refs,
3458	bool openWithOK)
3459{
3460	team_id team;
3461
3462	status_t error = B_ERROR;
3463	BString alertString;
3464
3465	for (int32 mimesetIt = 0; ; mimesetIt++) {
3466		error = be_roster->Launch(appRef, refs, &team);
3467		if (error == B_ALREADY_RUNNING)
3468			// app already running, not really an error
3469			error = B_OK;
3470
3471		if (error == B_OK)
3472			break;
3473
3474		if (mimesetIt > 0)
3475			break;
3476
3477		// failed to open, try mimesetting the refs and launching again
3478		SniffIfGeneric(refs);
3479	}
3480
3481	if (error == B_OK) {
3482		// close possible parent window, if specified
3483		const node_ref* nodeToClose = 0;
3484		ssize_t numBytes;
3485		if (refs != NULL && refs->FindData("nodeRefsToClose", B_RAW_TYPE,
3486				(const void**)&nodeToClose, &numBytes) == B_OK
3487			&& nodeToClose != NULL) {
3488			TTracker* tracker = dynamic_cast<TTracker*>(be_app);
3489			if (tracker != NULL)
3490				tracker->CloseParent(*nodeToClose);
3491		}
3492	} else {
3493		alertString.SetTo(B_TRANSLATE("Could not open \"%name\" (%error). "));
3494		alertString.ReplaceFirst("%name", appRef->name);
3495		alertString.ReplaceFirst("%error", strerror(error));
3496		if (refs != NULL && openWithOK && error != B_SHUTTING_DOWN) {
3497			alertString << B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3498			BAlert* alert = new BAlert("", alertString.String(),
3499				B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0,
3500				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3501			alert->SetShortcut(0, B_ESCAPE);
3502			if (alert->Go() == 1)
3503				error = TrackerOpenWith(refs);
3504		} else {
3505			BAlert* alert = new BAlert("", alertString.String(),
3506				B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
3507				B_WARNING_ALERT);
3508			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3509			alert->Go();
3510		}
3511	}
3512}
3513
3514
3515extern "C" char** environ;
3516
3517
3518static status_t
3519LoaderErrorDetails(const entry_ref* app, BString &details)
3520{
3521	BPath path;
3522	BEntry appEntry(app, true);
3523
3524	status_t result = appEntry.GetPath(&path);
3525	if (result != B_OK)
3526		return result;
3527
3528	char* argv[2] = { const_cast<char*>(path.Path()), 0};
3529
3530	port_id errorPort = create_port(1, "Tracker loader error");
3531
3532	// count environment variables
3533	int32 envCount = 0;
3534	while (environ[envCount] != NULL)
3535		envCount++;
3536
3537	char** flatArgs = NULL;
3538	size_t flatArgsSize;
3539	result = __flatten_process_args((const char**)argv, 1,
3540		environ, &envCount, argv[0], &flatArgs, &flatArgsSize);
3541	if (result != B_OK)
3542		return result;
3543
3544	result = _kern_load_image(flatArgs, flatArgsSize, 1, envCount,
3545		B_NORMAL_PRIORITY, B_WAIT_TILL_LOADED, errorPort, 0);
3546	if (result == B_OK) {
3547		// we weren't supposed to be able to start the application...
3548		return B_ERROR;
3549	}
3550
3551	// read error message from port and construct details string
3552
3553	ssize_t bufferSize;
3554
3555	do {
3556		bufferSize = port_buffer_size_etc(errorPort, B_RELATIVE_TIMEOUT, 0);
3557	} while (bufferSize == B_INTERRUPTED);
3558
3559	if (bufferSize <= B_OK) {
3560		delete_port(errorPort);
3561		return bufferSize;
3562	}
3563
3564	uint8* buffer = (uint8*)malloc(bufferSize);
3565	if (buffer == NULL) {
3566		delete_port(errorPort);
3567		return B_NO_MEMORY;
3568	}
3569
3570	bufferSize = read_port_etc(errorPort, NULL, buffer, bufferSize,
3571		B_RELATIVE_TIMEOUT, 0);
3572	delete_port(errorPort);
3573
3574	if (bufferSize < B_OK) {
3575		free(buffer);
3576		return bufferSize;
3577	}
3578
3579	BMessage message;
3580	result = message.Unflatten((const char*)buffer);
3581	free(buffer);
3582
3583	if (result != B_OK)
3584		return result;
3585
3586	int32 errorCode = B_ERROR;
3587	result = message.FindInt32("error", &errorCode);
3588	if (result != B_OK)
3589		return result;
3590
3591	const char* detailName = NULL;
3592	switch (errorCode) {
3593		case B_MISSING_LIBRARY:
3594			detailName = "missing library";
3595			break;
3596
3597		case B_MISSING_SYMBOL:
3598			detailName = "missing symbol";
3599			break;
3600	}
3601
3602	if (detailName == NULL)
3603		return B_ERROR;
3604
3605	const char* detail;
3606	for (int32 i = 0; message.FindString(detailName, i, &detail) == B_OK;
3607			i++) {
3608		if (i > 0)
3609			details += ", ";
3610		details += detail;
3611	}
3612
3613	return B_OK;
3614}
3615
3616
3617static void
3618_TrackerLaunchDocuments(const entry_ref*, const BMessage* refs,
3619	bool openWithOK)
3620{
3621	if (refs == NULL)
3622		return;
3623
3624	BMessage copyOfRefs(*refs);
3625
3626	entry_ref documentRef;
3627	if (copyOfRefs.FindRef("refs", &documentRef) != B_OK) {
3628		// nothing to launch, we are done
3629		return;
3630	}
3631
3632	status_t error = B_ERROR;
3633	entry_ref app;
3634	BMessage* refsToPass = NULL;
3635	BString alertString;
3636	const char* alternative = 0;
3637
3638	for (int32 mimesetIt = 0; ; mimesetIt++) {
3639		alertString = "";
3640		error = be_roster->FindApp(&documentRef, &app);
3641
3642		if (error != B_OK && mimesetIt == 0) {
3643			SniffIfGeneric(&copyOfRefs);
3644			continue;
3645		}
3646
3647		if (error != B_OK) {
3648			alertString.SetTo(B_TRANSLATE("Could not find an application to "
3649				"open \"%name\" (%error). "));
3650			alertString.ReplaceFirst("%name", documentRef.name);
3651			alertString.ReplaceFirst("%error", strerror(error));
3652			if (openWithOK)
3653				alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr);
3654
3655			break;
3656		} else {
3657			BEntry appEntry(&app, true);
3658			for (int32 index = 0;;) {
3659				// remove the app itself from the refs received so we don't
3660				// try to open ourselves
3661				entry_ref ref;
3662				if (copyOfRefs.FindRef("refs", index, &ref) != B_OK)
3663					break;
3664
3665				// deal with symlinks properly
3666				BEntry documentEntry(&ref, true);
3667				if (appEntry == documentEntry) {
3668					PRINT(("stripping %s, app %s \n", ref.name, app.name));
3669					copyOfRefs.RemoveData("refs", index);
3670				} else {
3671					PRINT(("leaving %s, app %s  \n", ref.name, app.name));
3672					index++;
3673				}
3674			}
3675
3676			refsToPass = CountRefs(&copyOfRefs) > 0 ? &copyOfRefs: 0;
3677			team_id team;
3678			error = be_roster->Launch(&app, refsToPass, &team);
3679			if (error == B_ALREADY_RUNNING)
3680				// app already running, not really an error
3681				error = B_OK;
3682			if (error == B_OK || mimesetIt != 0)
3683				break;
3684			if (error == B_LAUNCH_FAILED_EXECUTABLE) {
3685				BVolume volume(documentRef.device);
3686				if (volume.IsReadOnly()) {
3687					BMimeType type;
3688					error = BMimeType::GuessMimeType(&documentRef, &type);
3689					if (error != B_OK)
3690						break;
3691					error = be_roster->FindApp(type.Type(), &app);
3692					if (error != B_OK)
3693						break;
3694					error = be_roster->Launch(&app, refs, &team);
3695					if (error == B_ALREADY_RUNNING)
3696						// app already running, not really an error
3697						error = B_OK;
3698					if (error == B_OK || mimesetIt != 0)
3699						break;
3700				}
3701			}
3702
3703			SniffIfGeneric(&copyOfRefs);
3704		}
3705	}
3706
3707	if (error != B_OK && alertString.Length() == 0) {
3708		BString loaderErrorString;
3709		bool openedDocuments = true;
3710
3711		if (!refsToPass) {
3712			// we just double clicked the app itself, do not offer to
3713			// find a handling app
3714			openWithOK = false;
3715			openedDocuments = false;
3716		}
3717		if (error == B_UNKNOWN_EXECUTABLE && !refsToPass) {
3718			// We know it's an executable, but something unsupported
3719			alertString.SetTo(B_TRANSLATE("\"%name\" is an unsupported "
3720				"executable."));
3721			alertString.ReplaceFirst("%name", app.name);
3722		} else if (error == B_LEGACY_EXECUTABLE && !refsToPass) {
3723			// For the moment, this marks an old R3 binary, we may want to
3724			// extend it to gcc2 binaries someday post R1
3725			alertString.SetTo(B_TRANSLATE("\"%name\" is a legacy executable. "
3726				"Please obtain an updated version or recompile "
3727				"the application."));
3728			alertString.ReplaceFirst("%name", app.name);
3729		} else if (error == B_LAUNCH_FAILED_EXECUTABLE && !refsToPass) {
3730			alertString.SetTo(B_TRANSLATE("Could not open \"%name\". "
3731				"The file is mistakenly marked as executable. "));
3732			alertString.ReplaceFirst("%name", app.name);
3733
3734			if (!openWithOK) {
3735				// offer the possibility to change the permissions
3736
3737				alertString << B_TRANSLATE("\nShould this be fixed?");
3738				BAlert* alert = new BAlert("", alertString.String(),
3739					B_TRANSLATE("Cancel"), B_TRANSLATE("Proceed"), 0,
3740					B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3741				alert->SetShortcut(0, B_ESCAPE);
3742				if (alert->Go() == 1) {
3743					BEntry entry(&documentRef);
3744					mode_t permissions;
3745
3746					error = entry.GetPermissions(&permissions);
3747					if (error == B_OK) {
3748						error = entry.SetPermissions(permissions
3749							& ~(S_IXUSR | S_IXGRP | S_IXOTH));
3750					}
3751					if (error == B_OK) {
3752						// we updated the permissions, so let's try again
3753						_TrackerLaunchDocuments(NULL, refs, false);
3754						return;
3755					} else {
3756						alertString.SetTo(B_TRANSLATE("Could not update "
3757							"permissions of file \"%name\". %error"));
3758						alertString.ReplaceFirst("%name", app.name);
3759						alertString.ReplaceFirst("%error", strerror(error));
3760					}
3761				} else
3762					return;
3763			}
3764
3765			alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr);
3766		} else if (error == B_LAUNCH_FAILED_APP_IN_TRASH) {
3767			alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3768				"because application \"%app\" is in the Trash. "));
3769			alertString.ReplaceFirst("%document", documentRef.name);
3770			alertString.ReplaceFirst("%app", app.name);
3771			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3772		} else if (error == B_LAUNCH_FAILED_APP_NOT_FOUND) {
3773			alertString.SetTo(
3774				B_TRANSLATE("Could not open \"%name\" (%error). "));
3775			alertString.ReplaceFirst("%name", documentRef.name);
3776			alertString.ReplaceFirst("%error", strerror(error));
3777			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3778		} else if (error == B_MISSING_SYMBOL
3779			&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3780			if (openedDocuments) {
3781				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3782					"with application \"%app\" (Missing symbol: %symbol). "
3783					"\n"));
3784				alertString.ReplaceFirst("%document", documentRef.name);
3785				alertString.ReplaceFirst("%app", app.name);
3786				alertString.ReplaceFirst("%symbol",
3787					loaderErrorString.String());
3788			} else {
3789				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3790					"(Missing symbol: %symbol). \n"));
3791				alertString.ReplaceFirst("%document", documentRef.name);
3792				alertString.ReplaceFirst("%symbol",
3793					loaderErrorString.String());
3794			}
3795			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3796		} else if (error == B_MISSING_LIBRARY
3797			&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3798			if (openedDocuments) {
3799				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3800					"with application \"%app\" (Missing libraries: %library). "
3801					"\n"));
3802				alertString.ReplaceFirst("%document", documentRef.name);
3803				alertString.ReplaceFirst("%app", app.name);
3804				alertString.ReplaceFirst("%library",
3805					loaderErrorString.String());
3806			} else {
3807				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3808					"(Missing libraries: %library). \n"));
3809				alertString.ReplaceFirst("%document", documentRef.name);
3810				alertString.ReplaceFirst("%library",
3811					loaderErrorString.String());
3812			}
3813			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3814		} else {
3815			alertString.SetTo(B_TRANSLATE("Could not open \"%document\" with "
3816				"application \"%app\" (%error). "));
3817				alertString.ReplaceFirst("%document", documentRef.name);
3818				alertString.ReplaceFirst("%app", app.name);
3819				alertString.ReplaceFirst("%error", strerror(error));
3820			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3821		}
3822	}
3823
3824	if (error != B_OK) {
3825		if (openWithOK) {
3826			ASSERT(alternative);
3827			alertString << alternative;
3828			BAlert* alert = new BAlert("", alertString.String(),
3829				B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0,
3830				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3831			alert->SetShortcut(0, B_ESCAPE);
3832			if (alert->Go() == 1)
3833				error = TrackerOpenWith(refs);
3834		} else {
3835			BAlert* alert = new BAlert("", alertString.String(),
3836				B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
3837				B_WARNING_ALERT);
3838			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3839			alert->Go();
3840		}
3841	}
3842}
3843
3844// the following three calls don't return any reasonable error codes,
3845// should fix that, making them void
3846
3847status_t
3848TrackerLaunch(const entry_ref* appRef, const BMessage* refs, bool async,
3849	bool openWithOK)
3850{
3851	if (!async)
3852		_TrackerLaunchAppWithDocuments(appRef, refs, openWithOK);
3853	else {
3854		AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, refs,
3855			openWithOK);
3856	}
3857
3858	return B_OK;
3859}
3860
3861status_t
3862TrackerLaunch(const entry_ref* appRef, bool async)
3863{
3864	if (!async)
3865		_TrackerLaunchAppWithDocuments(appRef, NULL, false);
3866	else
3867		AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, 0, false);
3868
3869	return B_OK;
3870}
3871
3872status_t
3873TrackerLaunch(const BMessage* refs, bool async, bool openWithOK)
3874{
3875	if (!async)
3876		_TrackerLaunchDocuments(NULL, refs, openWithOK);
3877	else
3878		AsynchLaunchBinder(&_TrackerLaunchDocuments, NULL, refs, openWithOK);
3879
3880	return B_OK;
3881}
3882
3883
3884// external launch calls; need to be robust, work if Tracker is not running
3885
3886
3887#if !B_BEOS_VERSION_DANO
3888_IMPEXP_TRACKER
3889#endif
3890status_t
3891FSLaunchItem(const entry_ref* application, const BMessage* refsReceived,
3892	bool async, bool openWithOK)
3893{
3894	return TrackerLaunch(application, refsReceived, async, openWithOK);
3895}
3896
3897
3898#if !B_BEOS_VERSION_DANO
3899_IMPEXP_TRACKER
3900#endif
3901status_t
3902FSOpenWith(BMessage* listOfRefs)
3903{
3904	status_t result = B_ERROR;
3905	listOfRefs->what = B_REFS_RECEIVED;
3906
3907	if (dynamic_cast<TTracker*>(be_app) != NULL)
3908		result = TrackerOpenWith(listOfRefs);
3909	else
3910		ASSERT(!"not yet implemented");
3911
3912	return result;
3913}
3914
3915
3916// legacy calls, need for compatibility
3917
3918
3919void
3920FSOpenWithDocuments(const entry_ref* executable, BMessage* documents)
3921{
3922	TrackerLaunch(executable, documents, true);
3923	delete documents;
3924}
3925
3926
3927status_t
3928FSLaunchUsing(const entry_ref* ref, BMessage* listOfRefs)
3929{
3930	BMessage temp(B_REFS_RECEIVED);
3931	if (listOfRefs == NULL) {
3932		ASSERT(ref != NULL);
3933		temp.AddRef("refs", ref);
3934		listOfRefs = &temp;
3935	}
3936	FSOpenWith(listOfRefs);
3937
3938	return B_OK;
3939}
3940
3941
3942status_t
3943FSLaunchItem(const entry_ref* appRef, BMessage* refs, int32, bool async)
3944{
3945	if (refs != NULL)
3946		refs->what = B_REFS_RECEIVED;
3947
3948	status_t result = TrackerLaunch(appRef, refs, async, true);
3949	delete refs;
3950
3951	return result;
3952}
3953
3954
3955void
3956FSLaunchItem(const entry_ref* appRef, BMessage* refs, int32 workspace)
3957{
3958	FSLaunchItem(appRef, refs, workspace, true);
3959}
3960
3961
3962// Get the original path of an entry in the trash
3963status_t
3964FSGetOriginalPath(BEntry* entry, BPath* result)
3965{
3966	status_t err;
3967	entry_ref ref;
3968	err = entry->GetRef(&ref);
3969	if (err != B_OK)
3970		return err;
3971
3972	// Only call the routine for entries in the trash
3973	if (!FSInTrashDir(&ref))
3974		return B_ERROR;
3975
3976	BNode node(entry);
3977	BString originalPath;
3978	if (node.ReadAttrString(kAttrOriginalPath, &originalPath) == B_OK) {
3979		// We're in luck, the entry has the original path in an attribute
3980		err = result->SetTo(originalPath.String());
3981		return err;
3982	}
3983
3984	// Iterate the parent directories to find one with
3985	// the original path attribute
3986	BEntry parent(*entry);
3987	err = parent.InitCheck();
3988	if (err != B_OK)
3989		return err;
3990
3991	// walk up the directory structure until we find a node
3992	// with original path attribute
3993	do {
3994		// move to the parent of this node
3995		err = parent.GetParent(&parent);
3996		if (err != B_OK)
3997			return err;
3998
3999		// return if we are at the root of the trash
4000		if (FSIsTrashDir(&parent))
4001			return B_ENTRY_NOT_FOUND;
4002
4003		// get the parent as a node
4004		err = node.SetTo(&parent);
4005		if (err != B_OK)
4006			return err;
4007	} while (node.ReadAttrString(kAttrOriginalPath, &originalPath) != B_OK);
4008
4009	// Found the attribute, figure out there this file
4010	// used to live, based on the successfully-read attribute
4011	err = result->SetTo(originalPath.String());
4012	if (err != B_OK)
4013		return err;
4014
4015	BPath path, pathParent;
4016	err = parent.GetPath(&pathParent);
4017	if (err != B_OK)
4018		return err;
4019
4020	err = entry->GetPath(&path);
4021	if (err != B_OK)
4022		return err;
4023
4024	result->Append(path.Path() + strlen(pathParent.Path()) + 1);
4025		// compute the new path by appending the offset of
4026		// the item we are locating, to the original path
4027		// of the parent
4028
4029	return B_OK;
4030}
4031
4032
4033directory_which
4034WellKnowEntryList::Match(const node_ref* node)
4035{
4036	const WellKnownEntry* result = MatchEntry(node);
4037	if (result != NULL)
4038		return result->which;
4039
4040	return (directory_which)-1;
4041}
4042
4043
4044const WellKnowEntryList::WellKnownEntry*
4045WellKnowEntryList::MatchEntry(const node_ref* node)
4046{
4047	if (self == NULL)
4048		self = new WellKnowEntryList();
4049
4050	return self->MatchEntryCommon(node);
4051}
4052
4053
4054const WellKnowEntryList::WellKnownEntry*
4055WellKnowEntryList::MatchEntryCommon(const node_ref* node)
4056{
4057	uint32 count = entries.size();
4058	for (uint32 index = 0; index < count; index++) {
4059		if (*node == entries[index].node)
4060			return &entries[index];
4061	}
4062
4063	return NULL;
4064}
4065
4066
4067void
4068WellKnowEntryList::Quit()
4069{
4070	delete self;
4071	self = NULL;
4072}
4073
4074
4075void
4076WellKnowEntryList::AddOne(directory_which which, const char* name)
4077{
4078	BPath path;
4079	if (find_directory(which, &path, true) != B_OK)
4080		return;
4081
4082	BEntry entry(path.Path(), true);
4083	node_ref node;
4084	if (entry.GetNodeRef(&node) != B_OK)
4085		return;
4086
4087	entries.push_back(WellKnownEntry(&node, which, name));
4088}
4089
4090
4091void
4092WellKnowEntryList::AddOne(directory_which which, directory_which base,
4093	const char* extra, const char* name)
4094{
4095	BPath path;
4096	if (find_directory(base, &path, true) != B_OK)
4097		return;
4098
4099	path.Append(extra);
4100	BEntry entry(path.Path(), true);
4101	node_ref node;
4102	if (entry.GetNodeRef(&node) != B_OK)
4103		return;
4104
4105	entries.push_back(WellKnownEntry(&node, which, name));
4106}
4107
4108
4109void
4110WellKnowEntryList::AddOne(directory_which which, const char* path,
4111	const char* name)
4112{
4113	BEntry entry(path, true);
4114	node_ref node;
4115	if (entry.GetNodeRef(&node) != B_OK)
4116		return;
4117
4118	entries.push_back(WellKnownEntry(&node, which, name));
4119}
4120
4121
4122WellKnowEntryList::WellKnowEntryList()
4123{
4124	AddOne(B_SYSTEM_DIRECTORY, "system");
4125	AddOne((directory_which)B_BOOT_DISK, "/boot", "boot");
4126	AddOne(B_USER_DIRECTORY, "home");
4127
4128	AddOne(B_BEOS_FONTS_DIRECTORY, "fonts");
4129	AddOne(B_USER_FONTS_DIRECTORY, "fonts");
4130
4131	AddOne(B_BEOS_APPS_DIRECTORY, "apps");
4132	AddOne(B_APPS_DIRECTORY, "apps");
4133	AddOne((directory_which)B_USER_DESKBAR_APPS_DIRECTORY,
4134		B_USER_DESKBAR_DIRECTORY, "Applications", "apps");
4135
4136	AddOne(B_BEOS_PREFERENCES_DIRECTORY, "preferences");
4137	AddOne(B_PREFERENCES_DIRECTORY, "preferences");
4138	AddOne((directory_which)B_USER_DESKBAR_PREFERENCES_DIRECTORY,
4139		B_USER_DESKBAR_DIRECTORY, "Preferences", "preferences");
4140
4141	AddOne((directory_which)B_USER_MAIL_DIRECTORY, B_USER_DIRECTORY, "mail",
4142		"mail");
4143
4144	AddOne((directory_which)B_USER_QUERIES_DIRECTORY, B_USER_DIRECTORY,
4145		"queries", "queries");
4146
4147	AddOne(B_SYSTEM_DEVELOP_DIRECTORY, "develop");
4148	AddOne((directory_which)B_USER_DESKBAR_DEVELOP_DIRECTORY,
4149		B_USER_DESKBAR_DIRECTORY, "Development", "develop");
4150
4151	AddOne(B_USER_CONFIG_DIRECTORY, "config");
4152
4153	AddOne((directory_which)B_USER_PEOPLE_DIRECTORY, B_USER_DIRECTORY,
4154		"people", "people");
4155
4156	AddOne((directory_which)B_USER_DOWNLOADS_DIRECTORY, B_USER_DIRECTORY,
4157		"downloads", "downloads");
4158}
4159
4160WellKnowEntryList* WellKnowEntryList::self = NULL;
4161
4162} // namespace BPrivate
4163