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_FILE_EXISTS) {
1313			// A file with the same name was created after BDirectory::FindEntry was called and
1314			// before LowLevelCopy could finish.  In a move operation, if the standard file error
1315			// alert were displayed and the user chose to continue, the file that we just failed
1316			// to copy would be lost.  Don't offer the option to continue.
1317			BString lowLevelExistsString(
1318				B_TRANSLATE("Error copying file \"%name\":\n\t%error\n\n"));
1319			// The error may have resulted from the user dragging a set of selected files to a
1320			// case-insensitive volume, when 2 files in the set differ only in case.
1321			node_ref destRef;
1322			destDir->GetNodeRef(&destRef);
1323			fs_info destInfo;
1324			_kern_read_fs_info(destRef.device, &destInfo);
1325			if (strcmp(destInfo.fsh_name, "fat") == 0) {
1326				lowLevelExistsString += B_TRANSLATE("Note: file names in the destination file "
1327					"system are not case-sensitive.\n");
1328			}
1329			loopControl->FileError(lowLevelExistsString.String(), destName, err, false);
1330			throw (status_t)err;
1331		}
1332
1333		if (err != B_OK) {
1334			if (!loopControl->FileError(
1335					B_TRANSLATE_NOCOLLECT(kFileErrorString), destName, err,
1336					true)) {
1337				throw (status_t)err;
1338			} else {
1339				// user selected continue in spite of error, update status bar
1340				loopControl->UpdateStatus(NULL, ref, (int32)srcStat->st_size);
1341			}
1342		}
1343	}
1344}
1345
1346
1347#ifdef _SILENTLY_CORRECT_FILE_NAMES
1348static bool
1349CreateFileSystemCompatibleName(const BDirectory* destDir, char* destName)
1350{
1351	// Is it a FAT32 file system?
1352	// (this is the only one we currently know about)
1353
1354	BEntry target;
1355	destDir->GetEntry(&target);
1356	entry_ref targetRef;
1357	fs_info info;
1358	if (target.GetRef(&targetRef) == B_OK
1359		&& fs_stat_dev(targetRef.device, &info) == B_OK
1360		&& !strcmp(info.fsh_name, "fat")) {
1361		bool wasInvalid = false;
1362
1363		// it's a FAT32 file system, now check the name
1364
1365		int32 length = strlen(destName) - 1;
1366		while (destName[length] == '.') {
1367			// invalid name, just cut off the dot at the end
1368			destName[length--] = '\0';
1369			wasInvalid = true;
1370		}
1371
1372		char* invalid = destName;
1373		while ((invalid = strpbrk(invalid, "?<>\\:\"|*")) != NULL) {
1374			invalid[0] = '_';
1375			wasInvalid = true;
1376		}
1377
1378		return wasInvalid;
1379	}
1380
1381	return false;
1382}
1383#endif
1384
1385
1386static void
1387LowLevelCopy(BEntry* srcEntry, StatStruct* srcStat, BDirectory* destDir,
1388	char* destName, CopyLoopControl* loopControl, BPoint* loc)
1389{
1390	entry_ref ref;
1391	ThrowOnError(srcEntry->GetRef(&ref));
1392
1393	if (S_ISLNK(srcStat->st_mode)) {
1394		// handle symbolic links
1395		BSymLink srcLink;
1396		BSymLink newLink;
1397		char linkpath[MAXPATHLEN];
1398
1399		ThrowOnError(srcLink.SetTo(srcEntry));
1400		ssize_t size = srcLink.ReadLink(linkpath, MAXPATHLEN - 1);
1401		if (size < 0)
1402			ThrowOnError(size);
1403		ThrowOnError(destDir->CreateSymLink(destName, linkpath, &newLink));
1404
1405		node_ref destNodeRef;
1406		destDir->GetNodeRef(&destNodeRef);
1407		// copy or write new pose location as a first thing
1408		SetupPoseLocation(ref.directory, destNodeRef.node, &srcLink,
1409			&newLink, loc);
1410
1411		BNodeInfo nodeInfo(&newLink);
1412		nodeInfo.SetType(B_LINK_MIMETYPE);
1413
1414		newLink.SetPermissions(srcStat->st_mode);
1415		newLink.SetOwner(srcStat->st_uid);
1416		newLink.SetGroup(srcStat->st_gid);
1417		newLink.SetModificationTime(srcStat->st_mtime);
1418		newLink.SetCreationTime(srcStat->st_crtime);
1419
1420		return;
1421	}
1422
1423	BFile srcFile(srcEntry, O_RDONLY);
1424	ThrowOnInitCheckError(&srcFile);
1425
1426	const size_t kMinBufferSize = 1024* 128;
1427	const size_t kMaxBufferSize = 1024* 1024;
1428
1429	size_t bufsize = kMinBufferSize;
1430	if ((off_t)bufsize < srcStat->st_size) {
1431		// File bigger than the buffer size: determine an optimal buffer size
1432		system_info sinfo;
1433		get_system_info(&sinfo);
1434		size_t freesize = static_cast<size_t>(
1435			(sinfo.max_pages - sinfo.used_pages) * B_PAGE_SIZE);
1436		bufsize = freesize / 4;
1437			// take 1/4 of RAM max
1438		bufsize -= bufsize % (16* 1024);
1439			// Round to 16 KB boundaries
1440		if (bufsize < kMinBufferSize) {
1441			// at least kMinBufferSize
1442			bufsize = kMinBufferSize;
1443		} else if (bufsize > kMaxBufferSize) {
1444			// no more than kMaxBufferSize
1445			bufsize = kMaxBufferSize;
1446		}
1447	}
1448
1449	BFile destFile(destDir, destName, O_RDWR | O_CREAT);
1450#ifdef _SILENTLY_CORRECT_FILE_NAMES
1451	if ((destFile.InitCheck() == B_BAD_VALUE
1452		|| destFile.InitCheck() == B_NOT_ALLOWED)
1453		&& CreateFileSystemCompatibleName(destDir, destName)) {
1454		destFile.SetTo(destDir, destName, B_CREATE_FILE | B_READ_WRITE);
1455	}
1456#endif
1457
1458	ThrowOnInitCheckError(&destFile);
1459
1460	node_ref destNodeRef;
1461	destDir->GetNodeRef(&destNodeRef);
1462	// copy or write new pose location as a first thing
1463	SetupPoseLocation(ref.directory, destNodeRef.node, &srcFile,
1464		&destFile, loc);
1465
1466	char* buffer = new char[bufsize];
1467	try {
1468		// copy data portion of file
1469		while (true) {
1470			if (loopControl->CheckUserCanceled()) {
1471				// if copy was canceled, remove partial destination file
1472				destFile.Unset();
1473
1474				BEntry destEntry;
1475				if (destDir->FindEntry(destName, &destEntry) == B_OK)
1476					destEntry.Remove();
1477
1478				throw (status_t)kCopyCanceled;
1479			}
1480
1481			ASSERT(buffer);
1482			ssize_t bytes = srcFile.Read(buffer, bufsize);
1483
1484			if (bytes > 0) {
1485				ssize_t updateBytes = 0;
1486				if (bytes > 32* 1024) {
1487					// when copying large chunks, update after read and after
1488					// write to get better update granularity
1489					updateBytes = bytes / 2;
1490					loopControl->UpdateStatus(NULL, ref, updateBytes, true);
1491				}
1492
1493				loopControl->ChecksumChunk(buffer, (size_t)bytes);
1494
1495				ssize_t result = destFile.Write(buffer, (size_t)bytes);
1496				if (result != bytes) {
1497					if (result < 0)
1498						throw (status_t)result;
1499					throw (status_t)B_ERROR;
1500				}
1501
1502				result = destFile.Sync();
1503				if (result != B_OK)
1504					throw (status_t)result;
1505
1506				loopControl->UpdateStatus(NULL, ref, bytes - updateBytes,
1507					true);
1508			} else if (bytes < 0) {
1509				// read error
1510				throw (status_t)bytes;
1511			} else {
1512				// we are done
1513				break;
1514			}
1515		}
1516
1517		CopyAttributes(loopControl, &srcFile, &destFile, buffer, bufsize);
1518	} catch (...) {
1519		delete[] buffer;
1520		throw;
1521	}
1522
1523	destFile.SetPermissions(srcStat->st_mode);
1524	destFile.SetOwner(srcStat->st_uid);
1525	destFile.SetGroup(srcStat->st_gid);
1526	destFile.SetModificationTime(srcStat->st_mtime);
1527	destFile.SetCreationTime(srcStat->st_crtime);
1528
1529	delete[] buffer;
1530
1531	if (!loopControl->ChecksumFile(&ref)) {
1532		// File no good.  Remove and quit.
1533		destFile.Unset();
1534
1535		BEntry destEntry;
1536		if (destDir->FindEntry(destName, &destEntry) == B_OK)
1537			destEntry.Remove();
1538		throw (status_t)kUserCanceled;
1539	}
1540}
1541
1542
1543void
1544CopyAttributes(CopyLoopControl* control, BNode* srcNode, BNode* destNode,
1545	void* buffer, size_t bufsize)
1546{
1547	// ToDo:
1548	// Add error checking
1549	// prior to coyping attributes, make sure indices are installed
1550
1551	// When calling CopyAttributes on files, have to make sure destNode
1552	// is a BFile opened R/W
1553
1554	srcNode->RewindAttrs();
1555	char name[256];
1556	while (srcNode->GetNextAttrName(name) == B_OK) {
1557		// Check to see if this attribute should be skipped.
1558		if (control->SkipAttribute(name))
1559			continue;
1560
1561		attr_info info;
1562		if (srcNode->GetAttrInfo(name, &info) != B_OK)
1563			continue;
1564
1565		// Check to see if this attribute should be overwritten when it
1566		// already exists.
1567		if (control->PreserveAttribute(name)) {
1568			attr_info dest_info;
1569			if (destNode->GetAttrInfo(name, &dest_info) == B_OK)
1570				continue;
1571		}
1572
1573		// Special case for a size 0 attribute. It wouldn't be written at all
1574		// otherwise.
1575		if (info.size == 0)
1576			destNode->WriteAttr(name, info.type, 0, buffer, 0);
1577
1578		ssize_t bytes;
1579		ssize_t numToRead = (ssize_t)info.size;
1580		for (off_t offset = 0; numToRead > 0; offset += bytes) {
1581			size_t chunkSize = (size_t)numToRead;
1582			if (chunkSize > bufsize)
1583				chunkSize = bufsize;
1584
1585			bytes = srcNode->ReadAttr(name, info.type, offset,
1586				buffer, chunkSize);
1587
1588			if (bytes <= 0)
1589				break;
1590
1591			destNode->WriteAttr(name, info.type, offset, buffer,
1592				(size_t)bytes);
1593
1594			numToRead -= bytes;
1595		}
1596	}
1597}
1598
1599
1600static void
1601CopyFolder(BEntry* srcEntry, BDirectory* destDir,
1602	CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName,
1603	Undo &undo, bool removeSource = false)
1604{
1605	BDirectory newDir;
1606	BEntry entry;
1607	status_t err = B_OK;
1608	bool createDirectory = true;
1609	BEntry existingEntry;
1610
1611	if (loopControl->SkipEntry(srcEntry, false))
1612		return;
1613
1614	entry_ref ref;
1615	srcEntry->GetRef(&ref);
1616
1617	char destName[B_FILE_NAME_LENGTH];
1618	strlcpy(destName, ref.name, sizeof(destName));
1619
1620	loopControl->UpdateStatus(ref.name, ref, 1024, true);
1621
1622	if (makeOriginalName) {
1623		BString suffix(" ");
1624		suffix << B_TRANSLATE_COMMENT("copy", "filename copy"),
1625		FSMakeOriginalName(destName, destDir, suffix.String());
1626		undo.UpdateEntry(srcEntry, destName);
1627	}
1628
1629	if (destDir->FindEntry(destName, &existingEntry) == B_OK) {
1630		// some entry with a conflicting name is already present in destDir
1631		// decide what to do about it
1632		bool isDirectory = existingEntry.IsDirectory();
1633
1634		switch (loopControl->OverwriteOnConflict(srcEntry, destName, destDir,
1635			true, isDirectory)) {
1636			case TrackerCopyLoopControl::kSkip:
1637				// we are about to ignore this entire directory
1638				return;
1639
1640
1641			case TrackerCopyLoopControl::kReplace:
1642				if (!isDirectory) {
1643					// conflicting with a file or symbolic link, remove entry
1644					ThrowOnError(existingEntry.Remove());
1645					break;
1646				}
1647			// fall through if directory, do not replace.
1648			case TrackerCopyLoopControl::kMerge:
1649				ASSERT(isDirectory);
1650				// do not create a new directory, use the current one
1651				newDir.SetTo(&existingEntry);
1652				createDirectory = false;
1653				break;
1654		}
1655	}
1656
1657	// loop through everything in src folder and copy it to new folder
1658	BDirectory srcDir(srcEntry);
1659	srcDir.Rewind();
1660
1661	// create a new folder inside of destination folder
1662	if (createDirectory) {
1663	 	err = destDir->CreateDirectory(destName, &newDir);
1664#ifdef _SILENTLY_CORRECT_FILE_NAMES
1665	 	if (err == B_BAD_VALUE) {
1666	 		// check if it's an invalid name on a FAT32 file system
1667	 		if (CreateFileSystemCompatibleName(destDir, destName))
1668	 			err = destDir->CreateDirectory(destName, &newDir);
1669	 	}
1670#endif
1671		if (err != B_OK) {
1672			if (!loopControl->FileError(B_TRANSLATE_NOCOLLECT(
1673					kFolderErrorString), destName, err, true)) {
1674				throw err;
1675			}
1676
1677			// will allow rest of copy to continue
1678			return;
1679		}
1680	}
1681
1682	char* buffer;
1683	if (createDirectory && err == B_OK
1684		&& (buffer = (char*)malloc(32768)) != 0) {
1685		CopyAttributes(loopControl, &srcDir, &newDir, buffer, 32768);
1686			// don't copy original pose location if new location passed
1687		free(buffer);
1688	}
1689
1690	StatStruct statbuf;
1691	srcDir.GetStat(&statbuf);
1692	dev_t sourceDeviceID = statbuf.st_dev;
1693
1694	// copy or write new pose location
1695	node_ref destNodeRef;
1696	destDir->GetNodeRef(&destNodeRef);
1697	SetupPoseLocation(ref.directory, destNodeRef.node, &srcDir,
1698		&newDir, loc);
1699
1700	while (srcDir.GetNextEntry(&entry) == B_OK) {
1701
1702		if (loopControl->CheckUserCanceled())
1703			throw (status_t)kUserCanceled;
1704
1705		entry.GetStat(&statbuf);
1706
1707		if (S_ISDIR(statbuf.st_mode)) {
1708
1709			// entry is a mount point, do not copy it
1710			if (statbuf.st_dev != sourceDeviceID) {
1711				PRINT(("Avoiding mount point %" B_PRIdDEV ", %" B_PRIdDEV "\n",
1712					statbuf.st_dev, sourceDeviceID));
1713				continue;
1714			}
1715
1716			CopyFolder(&entry, &newDir, loopControl, 0, false, undo,
1717				removeSource);
1718			if (removeSource)
1719				FSDeleteFolder(&entry, loopControl, true, true, false);
1720		} else if (S_ISREG(statbuf.st_mode) || S_ISLNK(statbuf.st_mode)) {
1721			CopyFile(&entry, &statbuf, &newDir, loopControl, 0, false, undo);
1722			if (removeSource)
1723				entry.Remove();
1724		} else {
1725			// Ignore special files
1726		}
1727	}
1728	if (removeSource)
1729		srcEntry->Remove();
1730	else
1731		srcEntry->Unset();
1732}
1733
1734
1735status_t
1736RecursiveMove(BEntry* entry, BDirectory* destDir,
1737	CopyLoopControl* loopControl)
1738{
1739	const char* name = entry->Name();
1740
1741	if (destDir->Contains(name)) {
1742		BPath path (destDir, name);
1743		BDirectory subDir (path.Path());
1744		entry_ref ref;
1745		entry->GetRef(&ref);
1746		BDirectory source(&ref);
1747		if (source.InitCheck() == B_OK) {
1748			source.Rewind();
1749			BEntry current;
1750			while (source.GetNextEntry(&current) == B_OK) {
1751				if (current.IsDirectory()) {
1752					RecursiveMove(&current, &subDir, loopControl);
1753					current.Remove();
1754				} else {
1755					name = current.Name();
1756					if (loopControl->OverwriteOnConflict(&current, name,
1757							&subDir, true, false)
1758								!= TrackerCopyLoopControl::kSkip) {
1759						MoveError::FailOnError(current.MoveTo(&subDir,
1760							NULL, true));
1761					}
1762				}
1763			}
1764		}
1765		entry->Remove();
1766	} else
1767		MoveError::FailOnError(entry->MoveTo(destDir));
1768
1769	return B_OK;
1770}
1771
1772status_t
1773MoveItem(BEntry* entry, BDirectory* destDir, BPoint* loc, uint32 moveMode,
1774	const char* newName, Undo &undo, CopyLoopControl* loopControl)
1775{
1776	entry_ref ref;
1777	try {
1778		node_ref destNode;
1779		StatStruct statbuf;
1780		MoveError::FailOnError(entry->GetStat(&statbuf));
1781		MoveError::FailOnError(entry->GetRef(&ref));
1782		MoveError::FailOnError(destDir->GetNodeRef(&destNode));
1783
1784		if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) {
1785			PoseInfo poseInfo;
1786			char name[B_FILE_NAME_LENGTH];
1787			strlcpy(name, ref.name, sizeof(name));
1788
1789			BSymLink link;
1790			BString suffix(" ");
1791			suffix << B_TRANSLATE_COMMENT("link", "filename link"),
1792			FSMakeOriginalName(name, destDir, suffix.String());
1793			undo.UpdateEntry(entry, name);
1794
1795			BPath path;
1796			entry->GetPath(&path);
1797			if (loc && loc != (BPoint*)-1) {
1798				poseInfo.fInvisible = false;
1799				poseInfo.fInitedDirectory = destNode.node;
1800				poseInfo.fLocation = *loc;
1801			}
1802
1803			status_t err = B_ERROR;
1804
1805			if (moveMode == kCreateRelativeLink) {
1806				if (statbuf.st_dev == destNode.device) {
1807					// relative link only works on the same device
1808					char oldwd[B_PATH_NAME_LENGTH];
1809					getcwd(oldwd, B_PATH_NAME_LENGTH);
1810
1811					BEntry destEntry;
1812					destDir -> GetEntry(&destEntry);
1813					BPath destPath;
1814					destEntry.GetPath(&destPath);
1815
1816					chdir(destPath.Path());
1817						// change working dir to target dir
1818
1819					BString destString(destPath.Path());
1820					destString.Append("/");
1821
1822					BString srcString(path.Path());
1823					srcString.RemoveLast(path.Leaf());
1824
1825					// find index while paths are the same
1826
1827					const char* src = srcString.String();
1828					const char* dest = destString.String();
1829					const char* lastFolderSrc = src;
1830					const char* lastFolderDest = dest;
1831
1832					while (*src && *dest && *src == *dest) {
1833						++src;
1834						if (*dest++ == '/') {
1835							lastFolderSrc = src;
1836							lastFolderDest = dest;
1837						}
1838					}
1839					src = lastFolderSrc;
1840					dest = lastFolderDest;
1841
1842					BString source;
1843					if (*dest == '\0' && *src != '\0') {
1844						// source is deeper in the same tree than the target
1845						source.Append(src);
1846					} else if (*dest != '\0') {
1847						// target is deeper in the same tree than the source
1848						while (*dest) {
1849							if (*dest == '/')
1850								source.Prepend("../");
1851							++dest;
1852						}
1853						source.Append(src);
1854					}
1855
1856					// else source and target are in the same dir
1857
1858					source.Append(path.Leaf());
1859					err = destDir->CreateSymLink(name, source.String(),
1860						&link);
1861
1862					chdir(oldwd);
1863						// change working dir back to original
1864				} else
1865					moveMode = kCreateLink;
1866						// fall back to absolute link mode
1867			}
1868
1869			if (moveMode == kCreateLink)
1870				err = destDir->CreateSymLink(name, path.Path(), &link);
1871
1872			if (err == B_UNSUPPORTED) {
1873				throw FailWithAlert(err,
1874					B_TRANSLATE("The target disk does not support "
1875					"creating links."), NULL);
1876			}
1877
1878			FailWithAlert::FailOnError(err,
1879				B_TRANSLATE("Error creating link to \"%name\"."),
1880				ref.name);
1881
1882			if (loc && loc != (BPoint*)-1) {
1883				link.WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
1884					sizeof(PoseInfo));
1885			}
1886
1887			BNodeInfo nodeInfo(&link);
1888			nodeInfo.SetType(B_LINK_MIMETYPE);
1889			return B_OK;
1890		}
1891
1892		// if move is on same volume don't copy
1893		if (statbuf.st_dev == destNode.device && moveMode != kCopySelectionTo
1894			&& moveMode != kDuplicateSelection) {
1895
1896			// for "Move" the size for status is always 1 - since file
1897			// size is irrelevant when simply moving to a new folder
1898			loopControl->UpdateStatus(ref.name, ref, 1);
1899			if (entry->IsDirectory())
1900				return RecursiveMove(entry, destDir, loopControl);
1901
1902			MoveError::FailOnError(entry->MoveTo(destDir, newName));
1903		} else {
1904			bool makeOriginalName = (moveMode == kDuplicateSelection);
1905			if (S_ISDIR(statbuf.st_mode)) {
1906				CopyFolder(entry, destDir, loopControl, loc, makeOriginalName,
1907					undo, moveMode == kMoveSelectionTo);
1908			} else {
1909				CopyFile(entry, &statbuf, destDir, loopControl, loc,
1910					makeOriginalName, undo);
1911				if (moveMode == kMoveSelectionTo)
1912					entry->Remove();
1913			}
1914		}
1915	} catch (status_t error) {
1916		// no alert, was already taken care of before
1917		return error;
1918	} catch (MoveError& error) {
1919		BString errorString(B_TRANSLATE("Error moving \"%name\""));
1920		errorString.ReplaceFirst("%name", ref.name);
1921		BAlert* alert = new BAlert("", errorString.String(), B_TRANSLATE("OK"),
1922			0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1923		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1924		alert->Go();
1925		return error.fError;
1926	} catch (FailWithAlert& error) {
1927		BString buffer(error.fString);
1928		if (error.fName != NULL)
1929			buffer.ReplaceFirst("%name", error.fName);
1930		else
1931			buffer << error.fString;
1932
1933		BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("OK"),
1934			0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1935		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1936		alert->Go();
1937
1938		return error.fError;
1939	}
1940
1941	return B_OK;
1942}
1943
1944
1945void
1946FSDuplicate(BObjectList<entry_ref>* srcList, BList* pointList)
1947{
1948	LaunchInNewThread("DupTask", B_NORMAL_PRIORITY, MoveTask, srcList,
1949		(BEntry*)NULL, pointList, kDuplicateSelection);
1950}
1951
1952
1953#if 0
1954status_t
1955FSCopyFolder(BEntry* srcEntry, BDirectory* destDir,
1956	CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName)
1957{
1958	try
1959		CopyFolder(srcEntry, destDir, loopControl, loc, makeOriginalName);
1960	catch (status_t error) {
1961		return error;
1962
1963	return B_OK;
1964}
1965#endif
1966
1967
1968status_t
1969FSCopyAttributesAndStats(BNode* srcNode, BNode* destNode, bool copyTimes)
1970{
1971	char* buffer = new char[1024];
1972
1973	// copy the attributes
1974	srcNode->RewindAttrs();
1975	char name[256];
1976	while (srcNode->GetNextAttrName(name) == B_OK) {
1977		attr_info info;
1978		if (srcNode->GetAttrInfo(name, &info) != B_OK)
1979			continue;
1980
1981		attr_info dest_info;
1982		if (destNode->GetAttrInfo(name, &dest_info) == B_OK)
1983			continue;
1984
1985		ssize_t bytes;
1986		ssize_t numToRead = (ssize_t)info.size;
1987		for (off_t offset = 0; numToRead > 0; offset += bytes) {
1988			size_t chunkSize = (size_t)numToRead;
1989			if (chunkSize > 1024)
1990				chunkSize = 1024;
1991
1992			bytes = srcNode->ReadAttr(name, info.type, offset, buffer,
1993				chunkSize);
1994
1995			if (bytes <= 0)
1996				break;
1997
1998			destNode->WriteAttr(name, info.type, offset, buffer,
1999				(size_t)bytes);
2000
2001			numToRead -= bytes;
2002		}
2003	}
2004	delete[] buffer;
2005
2006	// copy the file stats
2007	struct stat srcStat;
2008	srcNode->GetStat(&srcStat);
2009	destNode->SetPermissions(srcStat.st_mode);
2010	destNode->SetOwner(srcStat.st_uid);
2011	destNode->SetGroup(srcStat.st_gid);
2012	if (copyTimes) {
2013		destNode->SetModificationTime(srcStat.st_mtime);
2014		destNode->SetCreationTime(srcStat.st_crtime);
2015	}
2016
2017	return B_OK;
2018}
2019
2020
2021#if 0
2022status_t
2023FSCopyFile(BEntry* srcFile, StatStruct* srcStat, BDirectory* destDir,
2024	CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName)
2025{
2026	try {
2027		CopyFile(srcFile, srcStat, destDir, loopControl, loc,
2028			makeOriginalName);
2029	} catch (status_t error) {
2030		return error;
2031	}
2032
2033	return B_OK;
2034}
2035#endif
2036
2037
2038static status_t
2039MoveEntryToTrash(BEntry* entry, BPoint* loc, Undo &undo)
2040{
2041	BDirectory trash_dir;
2042	entry_ref ref;
2043	status_t result = entry->GetRef(&ref);
2044	if (result != B_OK)
2045		return result;
2046
2047	node_ref nodeRef;
2048	result = entry->GetNodeRef(&nodeRef);
2049	if (result != B_OK)
2050		return result;
2051
2052	StatStruct statbuf;
2053	result = entry->GetStat(&statbuf);
2054	if (entry->GetStat(&statbuf) != B_OK)
2055		return result;
2056
2057	// if it's a directory close the window and any child dir windows
2058	if (S_ISDIR(statbuf.st_mode)) {
2059		BDirectory dir(entry);
2060
2061		// if it's a volume, try to unmount
2062		if (dir.IsRootDirectory()) {
2063			BVolume volume(nodeRef.device);
2064			BVolume boot;
2065
2066			BVolumeRoster().GetBootVolume(&boot);
2067			if (volume == boot) {
2068				char name[B_FILE_NAME_LENGTH];
2069				volume.GetName(name);
2070				BString buffer(
2071					B_TRANSLATE("Cannot unmount the boot volume \"%name\"."));
2072				buffer.ReplaceFirst("%name", name);
2073				BAlert* alert = new BAlert("", buffer.String(),
2074					B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
2075					B_WARNING_ALERT);
2076				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2077				alert->Go();
2078			} else {
2079				BMessage message(kUnmountVolume);
2080				message.AddInt32("device_id", volume.Device());
2081				be_app->PostMessage(&message);
2082			}
2083			return B_OK;
2084		}
2085
2086		// get trash directory on same volume as item being moved
2087		result = FSGetTrashDir(&trash_dir, nodeRef.device);
2088		if (result != B_OK)
2089			return result;
2090
2091		// check hierarchy before moving
2092		BEntry trashEntry;
2093		trash_dir.GetEntry(&trashEntry);
2094
2095		if (dir == trash_dir || dir.Contains(&trashEntry)) {
2096			BAlert* alert = new BAlert("",
2097				B_TRANSLATE("You cannot put the selected item(s) "
2098					"into the trash."),
2099				B_TRANSLATE("OK"),
2100				0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2101			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2102			alert->Go();
2103
2104			// return no error so we don't get two dialogs
2105			return B_OK;
2106		}
2107
2108		BMessage message(kCloseWindowAndChildren);
2109
2110		node_ref parentNode;
2111		parentNode.device = statbuf.st_dev;
2112		parentNode.node = statbuf.st_ino;
2113		message.AddData("node_ref", B_RAW_TYPE, &parentNode, sizeof(node_ref));
2114		be_app->PostMessage(&message);
2115	} else {
2116		// get trash directory on same volume as item being moved
2117		result = FSGetTrashDir(&trash_dir, nodeRef.device);
2118		if (result != B_OK)
2119			return result;
2120	}
2121
2122	// make sure name doesn't conflict with anything in trash already
2123	char name[B_FILE_NAME_LENGTH];
2124	strlcpy(name, ref.name, sizeof(name));
2125	if (trash_dir.Contains(name)) {
2126		BString suffix(" ");
2127		suffix << B_TRANSLATE_COMMENT("copy", "filename copy"),
2128		FSMakeOriginalName(name, &trash_dir, suffix.String());
2129		undo.UpdateEntry(entry, name);
2130	}
2131
2132	BNode* src_node = 0;
2133	if (loc && loc != (BPoint*)-1
2134		&& (src_node = GetWritableNode(entry, &statbuf)) != 0) {
2135		trash_dir.GetStat(&statbuf);
2136		PoseInfo poseInfo;
2137		poseInfo.fInvisible = false;
2138		poseInfo.fInitedDirectory = statbuf.st_ino;
2139		poseInfo.fLocation = *loc;
2140		src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
2141			sizeof(poseInfo));
2142		delete src_node;
2143	}
2144
2145	BNode node(entry);
2146	BPath path;
2147	// Get path of entry before it's moved to the trash
2148	// and write it to the file as an attribute
2149	if (node.InitCheck() == B_OK && entry->GetPath(&path) == B_OK) {
2150		BString originalPath(path.Path());
2151		node.WriteAttrString(kAttrOriginalPath, &originalPath);
2152	}
2153
2154	TrackerCopyLoopControl loopControl;
2155	MoveItem(entry, &trash_dir, loc, kMoveSelectionTo, name, undo,
2156		&loopControl);
2157	return B_OK;
2158}
2159
2160
2161ConflictCheckResult
2162PreFlightNameCheck(BObjectList<entry_ref>* srcList, const BDirectory* destDir,
2163	int32* collisionCount, uint32 moveMode)
2164{
2165	// count the number of name collisions in dest folder
2166	*collisionCount = 0;
2167
2168	int32 count = srcList->CountItems();
2169	for (int32 i = 0; i < count; i++) {
2170		entry_ref* srcRef = srcList->ItemAt(i);
2171		BEntry entry(srcRef);
2172		BDirectory parent;
2173		entry.GetParent(&parent);
2174
2175		if (parent != *destDir && destDir->Contains(srcRef->name))
2176			(*collisionCount)++;
2177	}
2178
2179	// prompt user only if there is more than one collision, otherwise the
2180	// single collision case will be handled as a "Prompt" case by CheckName
2181	if (*collisionCount > 1) {
2182		const char* verb = (moveMode == kMoveSelectionTo)
2183			? B_TRANSLATE("moving") : B_TRANSLATE("copying");
2184		BString replaceMsg(B_TRANSLATE_NOCOLLECT(kReplaceManyStr));
2185		replaceMsg.ReplaceAll("%verb", verb);
2186
2187		BAlert* alert = new BAlert();
2188		alert->SetText(replaceMsg.String());
2189		alert->AddButton(B_TRANSLATE("Cancel"));
2190		alert->AddButton(B_TRANSLATE("Prompt"));
2191		alert->AddButton(B_TRANSLATE("Skip all"));
2192		alert->AddButton(B_TRANSLATE("Replace all"));
2193		alert->SetShortcut(0, B_ESCAPE);
2194		switch (alert->Go()) {
2195			case 0:
2196				return kCanceled;
2197
2198			case 1:
2199				// user selected "Prompt"
2200				return kPrompt;
2201
2202			case 2:
2203				// user selected "Skip all"
2204				return kSkipAll;
2205
2206			case 3:
2207				// user selected "Replace all"
2208				return kReplaceAll;
2209		}
2210	}
2211
2212	return kNoConflicts;
2213}
2214
2215
2216void
2217FileStatToString(StatStruct* stat, char* buffer, int32 length)
2218{
2219	tm timeData;
2220	localtime_r(&stat->st_mtime, &timeData);
2221
2222	BString size;
2223	static BStringFormat format(
2224		B_TRANSLATE("{0, plural, one{# byte} other{# bytes}}"));
2225	format.Format(size, stat->st_size);
2226	uint32 pos = snprintf(buffer, length, "\n\t(%s ", size.String());
2227
2228	strftime(buffer + pos, length - pos, "%b %d %Y, %I:%M:%S %p)", &timeData);
2229}
2230
2231
2232status_t
2233CheckName(uint32 moveMode, const BEntry* sourceEntry,
2234	const BDirectory* destDir, bool multipleCollisions,
2235	ConflictCheckResult& conflictResolution)
2236{
2237	if (moveMode == kDuplicateSelection) {
2238		// when duplicating, we will never have a conflict
2239		return B_OK;
2240	}
2241
2242	// see if item already exists in destination dir
2243	const char* name = sourceEntry->Name();
2244	bool sourceIsDirectory = sourceEntry->IsDirectory();
2245
2246	BDirectory srcDirectory;
2247	if (sourceIsDirectory) {
2248		srcDirectory.SetTo(sourceEntry);
2249		BEntry destEntry;
2250		destDir->GetEntry(&destEntry);
2251
2252		if (moveMode != kCreateLink && moveMode != kCreateRelativeLink
2253			&& (srcDirectory == *destDir
2254				|| srcDirectory.Contains(&destEntry))) {
2255			BAlert* alert = new BAlert("",
2256				B_TRANSLATE("You can't move a folder into itself "
2257				"or any of its own sub-folders."), B_TRANSLATE("OK"),
2258				0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2259			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2260			alert->Go();
2261			return B_ERROR;
2262		}
2263	}
2264
2265	if (FSIsTrashDir(sourceEntry) && moveMode != kCreateLink
2266		&& moveMode != kCreateRelativeLink) {
2267		BAlert* alert = new BAlert("",
2268			B_TRANSLATE("You can't move or copy the trash."),
2269			B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL,
2270			B_WARNING_ALERT);
2271		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2272		alert->Go();
2273		return B_ERROR;
2274	}
2275
2276	BEntry entry;
2277	if (destDir->FindEntry(name, &entry) != B_OK) {
2278		// no conflict, return
2279		return B_OK;
2280	}
2281
2282	if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) {
2283		// if we are creating link in the same directory, the conflict will
2284		// be handled later by giving the link a unique name
2285		sourceEntry->GetParent(&srcDirectory);
2286
2287		if (srcDirectory == *destDir)
2288			return B_OK;
2289	}
2290
2291	bool destIsDir = entry.IsDirectory();
2292	// be sure not to replace the parent directory of the item being moved
2293	if (destIsDir) {
2294		BDirectory targetDir(&entry);
2295		if (targetDir.Contains(sourceEntry)) {
2296			BAlert* alert = new BAlert("",
2297				B_TRANSLATE("You can't replace a folder "
2298				"with one of its sub-folders."),
2299				B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2300			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2301			alert->Go();
2302			return B_ERROR;
2303		}
2304	}
2305
2306	// ensure that the user isn't trying to replace a file with folder
2307	// or vice-versa
2308	if (moveMode != kCreateLink
2309		&& moveMode != kCreateRelativeLink
2310		&& destIsDir != sourceIsDirectory) {
2311		BAlert* alert = new BAlert("", sourceIsDirectory
2312			? B_TRANSLATE("You cannot replace a file with a folder or a "
2313				"symbolic link.")
2314			: B_TRANSLATE("You cannot replace a folder or a symbolic link "
2315				"with a file."), B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL,
2316			B_WARNING_ALERT);
2317		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2318		alert->Go();
2319		return B_ERROR;
2320	}
2321
2322	if (conflictResolution == kSkipAll)
2323		return B_ERROR;
2324
2325	if (conflictResolution != kReplaceAll) {
2326		// prompt user to determine whether to replace or not
2327		BString replaceMsg;
2328
2329		if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) {
2330			replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kSymLinkReplaceStr));
2331			replaceMsg.ReplaceFirst("%name", name);
2332		} else if (sourceEntry->IsDirectory()) {
2333			replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kDirectoryReplaceStr));
2334			replaceMsg.ReplaceFirst("%name", name);
2335			replaceMsg.ReplaceFirst("%verb",
2336				moveMode == kMoveSelectionTo
2337				? B_TRANSLATE("moving")
2338				: B_TRANSLATE("copying"));
2339		} else {
2340			char sourceBuffer[96], destBuffer[96];
2341			StatStruct statBuffer;
2342
2343			if (!sourceEntry->IsDirectory()
2344				&& sourceEntry->GetStat(&statBuffer) == B_OK) {
2345				FileStatToString(&statBuffer, sourceBuffer, 96);
2346			} else
2347				sourceBuffer[0] = '\0';
2348
2349			if (!entry.IsDirectory() && entry.GetStat(&statBuffer) == B_OK)
2350				FileStatToString(&statBuffer, destBuffer, 96);
2351			else
2352				destBuffer[0] = '\0';
2353
2354			replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kReplaceStr));
2355			replaceMsg.ReplaceAll("%name", name);
2356			replaceMsg.ReplaceFirst("%dest", destBuffer);
2357			replaceMsg.ReplaceFirst("%src", sourceBuffer);
2358			replaceMsg.ReplaceFirst("%movemode", moveMode == kMoveSelectionTo
2359				? B_TRANSLATE("moving") : B_TRANSLATE("copying"));
2360		}
2361
2362		// special case single collision (don't need Replace All shortcut)
2363		BAlert* alert;
2364		if (multipleCollisions || sourceIsDirectory) {
2365			alert = new BAlert();
2366			alert->SetText(replaceMsg.String());
2367			alert->AddButton(B_TRANSLATE("Skip"));
2368			alert->AddButton(B_TRANSLATE("Skip all"));
2369			alert->AddButton(B_TRANSLATE("Replace"));
2370			alert->AddButton(B_TRANSLATE("Replace all"));
2371			switch (alert->Go()) {
2372				case 0:
2373					conflictResolution = kCanceled;
2374					return B_ERROR;
2375				case 1:
2376					conflictResolution = kSkipAll;
2377					return B_ERROR;
2378				case 2:
2379					conflictResolution = kReplace;
2380					break;
2381				case 3:
2382					conflictResolution = kReplaceAll;
2383					break;
2384			}
2385		} else {
2386			alert = new BAlert("", replaceMsg.String(),
2387				B_TRANSLATE("Cancel"), B_TRANSLATE("Replace"));
2388			alert->SetShortcut(0, B_ESCAPE);
2389			switch (alert->Go()) {
2390				case 0:
2391					conflictResolution = kCanceled;
2392					return B_ERROR;
2393				case 1:
2394					conflictResolution = kReplace;
2395					break;
2396			}
2397		}
2398	}
2399
2400	// delete destination item
2401	if (destIsDir)
2402		return B_OK;
2403
2404	status_t status = entry.Remove();
2405	if (status != B_OK) {
2406		BString error(B_TRANSLATE("There was a problem trying to replace "
2407			"\"%name\". The item might be open or busy."));
2408		error.ReplaceFirst("%name", name);
2409		BAlert* alert = new BAlert("", error.String(),
2410			B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2411		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2412		alert->Go();
2413	}
2414
2415	return status;
2416}
2417
2418
2419status_t
2420FSDeleteFolder(BEntry* dirEntry, CopyLoopControl* loopControl,
2421	bool updateStatus, bool deleteTopDir, bool upateFileNameInStatus)
2422{
2423	BDirectory dir(dirEntry);
2424
2425	// loop through everything in folder and delete it, skipping trouble files
2426	BEntry entry;
2427	while (dir.GetNextEntry(&entry) == B_OK) {
2428		entry_ref ref;
2429		entry.GetRef(&ref);
2430
2431		if (loopControl->CheckUserCanceled())
2432			return kTrashCanceled;
2433
2434		status_t status;
2435
2436		if (entry.IsDirectory())
2437			status = FSDeleteFolder(&entry, loopControl, updateStatus, true,
2438				upateFileNameInStatus);
2439		else {
2440			status = entry.Remove();
2441			if (updateStatus) {
2442				loopControl->UpdateStatus(upateFileNameInStatus ? ref.name
2443					: "", ref, 1, true);
2444			}
2445		}
2446
2447		if (status == kTrashCanceled)
2448			return kTrashCanceled;
2449
2450		if (status != B_OK) {
2451			loopControl->FileError(B_TRANSLATE_NOCOLLECT(
2452					kFileDeleteErrorString), ref.name, status, false);
2453		}
2454	}
2455
2456	if (loopControl->CheckUserCanceled())
2457		return kTrashCanceled;
2458
2459	entry_ref ref;
2460	dirEntry->GetRef(&ref);
2461
2462	if (updateStatus && deleteTopDir)
2463		loopControl->UpdateStatus(NULL, ref, 1);
2464
2465	if (deleteTopDir)
2466		return dirEntry->Remove();
2467
2468	return B_OK;
2469}
2470
2471
2472void
2473FSMakeOriginalName(BString &string, const BDirectory* destDir,
2474	const char* suffix)
2475{
2476	if (!destDir->Contains(string.String()))
2477		return;
2478
2479	FSMakeOriginalName(string.LockBuffer(B_FILE_NAME_LENGTH),
2480		const_cast<BDirectory*>(destDir), suffix ? suffix : " copy");
2481	string.UnlockBuffer();
2482}
2483
2484
2485void
2486FSMakeOriginalName(char* name, BDirectory* destDir, const char* suffix)
2487{
2488	char		root[B_FILE_NAME_LENGTH];
2489	char		copybase[B_FILE_NAME_LENGTH];
2490	char		tempName[B_FILE_NAME_LENGTH + 11];
2491	int32		fnum;
2492
2493	// is this name already original?
2494	if (!destDir->Contains(name))
2495		return;
2496
2497	// Determine if we're copying a 'copy'. This algorithm isn't perfect.
2498	// If you're copying a file whose REAL name ends with 'copy' then
2499	// this method will return "<filename> 1", not "<filename> copy"
2500
2501	// However, it will correctly handle file that contain 'copy'
2502	// elsewhere in their name.
2503
2504	bool copycopy = false;		// are we copying a copy?
2505	int32 len = (int32)strlen(name);
2506	char* p = name + len - 1;	// get pointer to end os name
2507
2508	// eat up optional numbers (if were copying "<filename> copy 34")
2509	while ((p > name) && isdigit(*p))
2510		p--;
2511
2512	// eat up optional spaces
2513	while ((p > name) && isspace(*p))
2514		p--;
2515
2516	// now look for the phrase " copy"
2517	if (p > name) {
2518		// p points to the last char of the word. For example, 'y' in 'copy'
2519
2520		if ((p - 4 > name) && (strncmp(p - 4, suffix, 5) == 0)) {
2521			// we found 'copy' in the right place.
2522			// so truncate after 'copy'
2523			*(p + 1) = '\0';
2524			copycopy = true;
2525
2526			// save the 'root' name of the file, for possible later use.
2527			// that is copy everything but trailing " copy". Need to
2528			// NULL terminate after copy
2529			strncpy(root, name, (uint32)((p - name) - 4));
2530			root[(p - name) - 4] = '\0';
2531		}
2532	}
2533
2534	if (!copycopy) {
2535		// The name can't be longer than B_FILE_NAME_LENGTH.
2536		// The algoritm adds " copy XX" to the name. That's 8 characters.
2537		// B_FILE_NAME_LENGTH already accounts for NULL termination so we
2538		// don't need to save an extra char at the end.
2539		if (strlen(name) > B_FILE_NAME_LENGTH - 8) {
2540			// name is too long - truncate it!
2541			name[B_FILE_NAME_LENGTH - 8] = '\0';
2542		}
2543
2544		strlcpy(root, name, sizeof(root));
2545			// save root name
2546		strlcat(name, suffix, B_FILE_NAME_LENGTH);
2547	}
2548
2549	strlcpy(copybase, name, sizeof(copybase));
2550
2551	// if name already exists then add a number
2552	fnum = 1;
2553	strlcpy(tempName, name, sizeof(tempName));
2554	while (destDir->Contains(tempName)) {
2555		snprintf(tempName, sizeof(tempName), "%s %" B_PRId32, copybase,
2556			++fnum);
2557
2558		if (strlen(tempName) > (B_FILE_NAME_LENGTH - 1)) {
2559			// The name has grown too long. Maybe we just went from
2560			// "<filename> copy 9" to "<filename> copy 10" and that extra
2561			// character was too much. The solution is to further
2562			// truncate the 'root' name and continue.
2563			// ??? should we reset fnum or not ???
2564			root[strlen(root) - 1] = '\0';
2565			snprintf(tempName, sizeof(tempName), "%s%s %" B_PRId32, root,
2566				suffix, fnum);
2567		}
2568	}
2569
2570	strlcpy(name, tempName, B_FILE_NAME_LENGTH);
2571}
2572
2573
2574status_t
2575FSRecursiveCalcSize(BInfoWindow* window, CopyLoopControl* loopControl,
2576	BDirectory* dir, off_t* _runningSize, int32* _fileCount, int32* _dirCount)
2577{
2578	dir->Rewind();
2579	BEntry entry;
2580	while (dir->GetNextEntry(&entry) == B_OK) {
2581		// be sure window hasn't closed
2582		if (window && window->StopCalc())
2583			return B_OK;
2584
2585		if (loopControl->CheckUserCanceled())
2586			return kUserCanceled;
2587
2588		StatStruct statbuf;
2589		status_t status = entry.GetStat(&statbuf);
2590		if (status != B_OK)
2591			return status;
2592
2593		(*_runningSize) += statbuf.st_blocks * 512;
2594
2595		if (S_ISDIR(statbuf.st_mode)) {
2596			BDirectory subdir(&entry);
2597			(*_dirCount)++;
2598			status = FSRecursiveCalcSize(window, loopControl, &subdir,
2599				_runningSize, _fileCount, _dirCount);
2600			if (status != B_OK)
2601				return status;
2602		} else
2603			(*_fileCount)++;
2604	}
2605	return B_OK;
2606}
2607
2608
2609status_t
2610CalcItemsAndSize(CopyLoopControl* loopControl,
2611	BObjectList<entry_ref>* refList, ssize_t blockSize, int32* totalCount,
2612	off_t* totalSize)
2613{
2614	int32 fileCount = 0;
2615	int32 dirCount = 0;
2616
2617	// check block size for sanity
2618	if (blockSize < 0) {
2619		// This would point at an error to retrieve the block size from
2620		// the target volume. The code below cannot be used, it is only
2621		// meant to get the block size when item operations happen on
2622		// the source volume.
2623		blockSize = 2048;
2624	} else if (blockSize < 1024) {
2625		blockSize = 1024;
2626		if (entry_ref* ref = refList->ItemAt(0)) {
2627			// TODO: This assumes all entries in the list share the same
2628			// volume...
2629			BVolume volume(ref->device);
2630			if (volume.InitCheck() == B_OK)
2631				blockSize = volume.BlockSize();
2632		}
2633	}
2634	// File systems like ReiserFS may advertize a large block size, but
2635	// stuff is still packed into blocks, so clamp maximum block size.
2636	if (blockSize > 8192)
2637		blockSize = 8192;
2638
2639	int32 num_items = refList->CountItems();
2640	for (int32 i = 0; i < num_items; i++) {
2641		entry_ref* ref = refList->ItemAt(i);
2642		BEntry entry(ref);
2643		StatStruct statbuf;
2644		entry.GetStat(&statbuf);
2645
2646		if (loopControl->CheckUserCanceled())
2647			return kUserCanceled;
2648
2649		if (S_ISDIR(statbuf.st_mode)) {
2650			BDirectory dir(&entry);
2651			dirCount++;
2652			(*totalSize) += blockSize;
2653			status_t result = FSRecursiveCalcSize(NULL, loopControl, &dir,
2654				totalSize, &fileCount, &dirCount);
2655			if (result != B_OK)
2656				return result;
2657		} else {
2658			fileCount++;
2659			(*totalSize) += statbuf.st_size + blockSize;
2660		}
2661	}
2662
2663	*totalCount += (fileCount + dirCount);
2664	return B_OK;
2665}
2666
2667
2668status_t
2669FSGetTrashDir(BDirectory* trashDir, dev_t dev)
2670{
2671	if (trashDir == NULL)
2672		return B_BAD_VALUE;
2673
2674	BVolume volume(dev);
2675	status_t result = volume.InitCheck();
2676	if (result != B_OK)
2677		return result;
2678
2679	BPath path;
2680	result = find_directory(B_TRASH_DIRECTORY, &path, false, &volume);
2681	if (result != B_OK)
2682		return result;
2683
2684	result = trashDir->SetTo(path.Path());
2685	if (result != B_OK) {
2686		// Trash directory does not exist yet, create it.
2687		result = create_directory(path.Path(), 0755);
2688		if (result != B_OK)
2689			return result;
2690
2691		result = trashDir->SetTo(path.Path());
2692		if (result != B_OK)
2693			return result;
2694
2695		// make Trash directory invisible
2696		StatStruct sbuf;
2697		trashDir->GetStat(&sbuf);
2698
2699		PoseInfo poseInfo;
2700		poseInfo.fInvisible = true;
2701		poseInfo.fInitedDirectory = sbuf.st_ino;
2702		trashDir->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
2703			sizeof(PoseInfo));
2704	}
2705
2706	// set Trash icons (if they haven't already been set)
2707	attr_info attrInfo;
2708	size_t size;
2709	const void* data;
2710	if (trashDir->GetAttrInfo(kAttrLargeIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2711		data = GetTrackerResources()->LoadResource('ICON', R_TrashIcon, &size);
2712		if (data != NULL)
2713			trashDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
2714	}
2715
2716	if (trashDir->GetAttrInfo(kAttrMiniIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2717		data = GetTrackerResources()->LoadResource('MICN', R_TrashIcon, &size);
2718		if (data != NULL)
2719			trashDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
2720	}
2721
2722	if (trashDir->GetAttrInfo(kAttrIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2723		data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE,
2724			R_TrashIcon, &size);
2725		if (data != NULL)
2726			trashDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
2727	}
2728
2729	return B_OK;
2730}
2731
2732
2733status_t
2734FSGetDeskDir(BDirectory* deskDir)
2735{
2736	if (deskDir == NULL)
2737		return B_BAD_VALUE;
2738
2739	BPath path;
2740	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true);
2741	if (result != B_OK)
2742		return result;
2743
2744	result = deskDir->SetTo(path.Path());
2745	if (result != B_OK)
2746		return result;
2747
2748	// set Desktop icons (if they haven't already been set)
2749	attr_info attrInfo;
2750	size_t size;
2751	const void* data;
2752	if (deskDir->GetAttrInfo(kAttrLargeIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2753		data = GetTrackerResources()->LoadResource('ICON', R_DeskIcon, &size);
2754		if (data != NULL)
2755			deskDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
2756	}
2757
2758	if (deskDir->GetAttrInfo(kAttrMiniIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2759		data = GetTrackerResources()->LoadResource('MICN', R_DeskIcon, &size);
2760		if (data != NULL)
2761			deskDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
2762	}
2763
2764	if (deskDir->GetAttrInfo(kAttrIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2765		data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE,
2766			R_DeskIcon, &size);
2767		if (data != NULL)
2768			deskDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
2769	}
2770
2771	return B_OK;
2772}
2773
2774
2775status_t
2776FSGetBootDeskDir(BDirectory* deskDir)
2777{
2778	BVolume bootVolume;
2779	BVolumeRoster().GetBootVolume(&bootVolume);
2780	BPath path;
2781
2782	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true,
2783		&bootVolume);
2784	if (result != B_OK)
2785		return result;
2786
2787	return deskDir->SetTo(path.Path());
2788}
2789
2790
2791static bool
2792FSIsDirFlavor(const BEntry* entry, directory_which directoryType)
2793{
2794	StatStruct dir_stat;
2795	StatStruct entry_stat;
2796	BVolume volume;
2797	BPath path;
2798
2799	if (entry->GetStat(&entry_stat) != B_OK)
2800		return false;
2801
2802	if (volume.SetTo(entry_stat.st_dev) != B_OK)
2803		return false;
2804
2805	if (find_directory(directoryType, &path, false, &volume) != B_OK)
2806		return false;
2807
2808	stat(path.Path(), &dir_stat);
2809
2810	return dir_stat.st_ino == entry_stat.st_ino
2811		&& dir_stat.st_dev == entry_stat.st_dev;
2812}
2813
2814
2815bool
2816FSIsPrintersDir(const BEntry* entry)
2817{
2818	return FSIsDirFlavor(entry, B_USER_PRINTERS_DIRECTORY);
2819}
2820
2821
2822bool
2823FSIsTrashDir(const BEntry* entry)
2824{
2825	return FSIsDirFlavor(entry, B_TRASH_DIRECTORY);
2826}
2827
2828
2829bool
2830FSIsDeskDir(const BEntry* entry)
2831{
2832	BPath path;
2833	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true);
2834	if (result != B_OK)
2835		return false;
2836
2837	BEntry entryToCompare(path.Path());
2838	return entryToCompare == *entry;
2839}
2840
2841
2842bool
2843FSIsHomeDir(const BEntry* entry)
2844{
2845	return FSIsDirFlavor(entry, B_USER_DIRECTORY);
2846}
2847
2848
2849bool
2850FSIsRootDir(const BEntry* entry)
2851{
2852	BPath path;
2853	if (entry->InitCheck() != B_OK || entry->GetPath(&path) != B_OK)
2854		return false;
2855
2856	return strcmp(path.Path(), "/") == 0;
2857}
2858
2859
2860bool
2861DirectoryMatchesOrContains(const BEntry* entry, directory_which which)
2862{
2863	BPath path;
2864	if (find_directory(which, &path, false, NULL) != B_OK)
2865		return false;
2866
2867	BEntry dirEntry(path.Path());
2868	if (dirEntry.InitCheck() != B_OK)
2869		return false;
2870
2871	if (dirEntry == *entry)
2872		// root level match
2873		return true;
2874
2875	BDirectory dir(&dirEntry);
2876	return dir.Contains(entry);
2877}
2878
2879
2880bool
2881DirectoryMatchesOrContains(const BEntry* entry, const char* additionalPath,
2882	directory_which which)
2883{
2884	BPath path;
2885	if (find_directory(which, &path, false, NULL) != B_OK)
2886		return false;
2887
2888	path.Append(additionalPath);
2889	BEntry dirEntry(path.Path());
2890	if (dirEntry.InitCheck() != B_OK)
2891		return false;
2892
2893	if (dirEntry == *entry)
2894		// root level match
2895		return true;
2896
2897	BDirectory dir(&dirEntry);
2898	return dir.Contains(entry);
2899}
2900
2901
2902bool
2903DirectoryMatches(const BEntry* entry, directory_which which)
2904{
2905	BPath path;
2906	if (find_directory(which, &path, false, NULL) != B_OK)
2907		return false;
2908
2909	BEntry dirEntry(path.Path());
2910	if (dirEntry.InitCheck() != B_OK)
2911		return false;
2912
2913	return dirEntry == *entry;
2914}
2915
2916
2917bool
2918DirectoryMatches(const BEntry* entry, const char* additionalPath,
2919	directory_which which)
2920{
2921	BPath path;
2922	if (find_directory(which, &path, false, NULL) != B_OK)
2923		return false;
2924
2925	path.Append(additionalPath);
2926	BEntry dirEntry(path.Path());
2927	if (dirEntry.InitCheck() != B_OK)
2928		return false;
2929
2930	return dirEntry == *entry;
2931}
2932
2933
2934extern status_t
2935FSFindTrackerSettingsDir(BPath* path, bool autoCreate)
2936{
2937	status_t result = find_directory(B_USER_SETTINGS_DIRECTORY, path,
2938		autoCreate);
2939	if (result != B_OK)
2940		return result;
2941
2942	path->Append("Tracker");
2943
2944	return mkdir(path->Path(), 0777) ? B_OK : errno;
2945}
2946
2947
2948bool
2949FSInTrashDir(const entry_ref* ref)
2950{
2951	BEntry entry(ref);
2952	if (entry.InitCheck() != B_OK)
2953		return false;
2954
2955	BDirectory trashDir;
2956	if (FSGetTrashDir(&trashDir, ref->device) != B_OK)
2957		return false;
2958
2959	return trashDir.Contains(&entry);
2960}
2961
2962
2963void
2964FSEmptyTrash()
2965{
2966	if (find_thread("_tracker_empty_trash_") != B_OK) {
2967		resume_thread(spawn_thread(empty_trash, "_tracker_empty_trash_",
2968			B_NORMAL_PRIORITY, NULL));
2969	}
2970}
2971
2972
2973status_t
2974empty_trash(void*)
2975{
2976	// empty trash on all mounted volumes
2977	status_t status = B_OK;
2978
2979	TrackerCopyLoopControl loopControl(kTrashState);
2980
2981	// calculate the sum total of all items on all volumes in trash
2982	BObjectList<entry_ref> srcList;
2983	int32 totalCount = 0;
2984	off_t totalSize = 0;
2985
2986	BVolumeRoster volumeRoster;
2987	BVolume volume;
2988	while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2989		if (volume.IsReadOnly() || !volume.IsPersistent())
2990			continue;
2991
2992		BDirectory trashDirectory;
2993		if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
2994			continue;
2995
2996		BEntry entry;
2997		trashDirectory.GetEntry(&entry);
2998
2999		entry_ref ref;
3000		entry.GetRef(&ref);
3001		srcList.AddItem(&ref);
3002		status = CalcItemsAndSize(&loopControl, &srcList, volume.BlockSize(),
3003			&totalCount, &totalSize);
3004		if (status != B_OK)
3005			break;
3006
3007		srcList.MakeEmpty();
3008
3009		// don't count trash directory itself
3010		totalCount--;
3011	}
3012
3013	if (status == B_OK) {
3014		loopControl.Init(totalCount, totalCount);
3015
3016		volumeRoster.Rewind();
3017		while (volumeRoster.GetNextVolume(&volume) == B_OK) {
3018			if (volume.IsReadOnly() || !volume.IsPersistent())
3019				continue;
3020
3021			BDirectory trashDirectory;
3022			if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
3023				continue;
3024
3025			BEntry entry;
3026			trashDirectory.GetEntry(&entry);
3027			status = FSDeleteFolder(&entry, &loopControl, true, false);
3028		}
3029	}
3030
3031	if (status != B_OK && status != kTrashCanceled && status != kUserCanceled) {
3032		BAlert* alert = new BAlert("", B_TRANSLATE("Error emptying Trash"),
3033			B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3034			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3035			alert->Go();
3036	}
3037
3038	return B_OK;
3039}
3040
3041
3042status_t
3043_DeleteTask(BObjectList<entry_ref>* list, bool confirm)
3044{
3045	if (confirm) {
3046		BAlert* alert = new BAlert("",
3047			B_TRANSLATE_NOCOLLECT(kDeleteConfirmationStr),
3048			B_TRANSLATE("Cancel"), B_TRANSLATE("Move to Trash"),
3049			B_TRANSLATE("Delete"),
3050			B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
3051
3052		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3053		alert->SetShortcut(0, B_ESCAPE);
3054		alert->SetShortcut(1, 'm');
3055		alert->SetShortcut(2, 'd');
3056
3057		switch (alert->Go()) {
3058			case 0:
3059				delete list;
3060				return B_CANCELED;
3061
3062			case 1:
3063			default:
3064				FSMoveToTrash(list, NULL, false);
3065				return B_OK;
3066
3067			case 2:
3068				break;
3069		}
3070	}
3071
3072	TrackerCopyLoopControl loopControl(kDeleteState);
3073
3074	// calculate the sum total of all items on all volumes in trash
3075	int32 totalItems = 0;
3076	int64 totalSize = 0;
3077
3078	status_t status = CalcItemsAndSize(&loopControl, list, 0, &totalItems,
3079		&totalSize);
3080	if (status == B_OK) {
3081		loopControl.Init(totalItems, totalItems);
3082
3083		int32 count = list->CountItems();
3084		for (int32 index = 0; index < count; index++) {
3085			entry_ref ref(*list->ItemAt(index));
3086			BEntry entry(&ref);
3087			loopControl.UpdateStatus(ref.name, ref, 1, true);
3088			if (entry.IsDirectory())
3089				status = FSDeleteFolder(&entry, &loopControl, true, true, true);
3090			else
3091				status = entry.Remove();
3092		}
3093
3094		if (status != kTrashCanceled && status != kUserCanceled
3095			&& status != B_OK) {
3096			BAlert* alert = new BAlert("", B_TRANSLATE("Error deleting items"),
3097				B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
3098				B_WARNING_ALERT);
3099			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3100			alert->Go();
3101		}
3102	}
3103
3104	delete list;
3105
3106	return B_OK;
3107}
3108
3109status_t
3110FSRecursiveCreateFolder(BPath path)
3111{
3112	BEntry entry(path.Path());
3113	if (entry.InitCheck() != B_OK) {
3114		BPath parentPath;
3115		status_t err = path.GetParent(&parentPath);
3116		if (err != B_OK)
3117			return err;
3118
3119		err = FSRecursiveCreateFolder(parentPath);
3120		if (err != B_OK)
3121			return err;
3122	}
3123
3124	entry.SetTo(path.Path());
3125	if (entry.Exists())
3126		return B_FILE_EXISTS;
3127
3128	BDirectory parent;
3129	entry.GetParent(&parent);
3130	parent.CreateDirectory(entry.Name(), NULL);
3131
3132	return B_OK;
3133}
3134
3135status_t
3136_RestoreTask(BObjectList<entry_ref>* list)
3137{
3138	TrackerCopyLoopControl loopControl(kRestoreFromTrashState);
3139
3140	// calculate the sum total of all items that will be restored
3141	int32 totalItems = 0;
3142	int64 totalSize = 0;
3143
3144	status_t err = CalcItemsAndSize(&loopControl, list, 0, &totalItems,
3145		&totalSize);
3146	if (err == B_OK) {
3147		loopControl.Init(totalItems, totalItems);
3148
3149		int32 count = list->CountItems();
3150		for (int32 index = 0; index < count; index++) {
3151			entry_ref ref(*list->ItemAt(index));
3152			BEntry entry(&ref);
3153			BPath originalPath;
3154
3155			loopControl.UpdateStatus(ref.name, ref, 1, true);
3156
3157			if (FSGetOriginalPath(&entry, &originalPath) != B_OK)
3158				continue;
3159
3160			BEntry originalEntry(originalPath.Path());
3161			BPath parentPath;
3162			err = originalPath.GetParent(&parentPath);
3163			if (err != B_OK)
3164				continue;
3165			BEntry parentEntry(parentPath.Path());
3166
3167			if (parentEntry.InitCheck() != B_OK || !parentEntry.Exists()) {
3168				if (FSRecursiveCreateFolder(parentPath) == B_OK) {
3169					originalEntry.SetTo(originalPath.Path());
3170					if (entry.InitCheck() != B_OK)
3171						continue;
3172				}
3173			}
3174
3175			if (!originalEntry.Exists()) {
3176				BDirectory dir(parentPath.Path());
3177				if (dir.InitCheck() == B_OK) {
3178					const char* leafName = originalEntry.Name();
3179					if (entry.MoveTo(&dir, leafName) == B_OK) {
3180						BNode node(&entry);
3181						if (node.InitCheck() == B_OK)
3182							node.RemoveAttr(kAttrOriginalPath);
3183					}
3184				}
3185			}
3186
3187			err = loopControl.CheckUserCanceled();
3188			if (err != B_OK)
3189				break;
3190		}
3191	}
3192
3193	delete list;
3194
3195	return err;
3196}
3197
3198void
3199FSCreateTrashDirs()
3200{
3201	BVolume volume;
3202	BVolumeRoster roster;
3203
3204	roster.Rewind();
3205	while (roster.GetNextVolume(&volume) == B_OK) {
3206		if (volume.IsReadOnly() || !volume.IsPersistent())
3207			continue;
3208
3209		BDirectory trashDir;
3210		FSGetTrashDir(&trashDir, volume.Device());
3211	}
3212}
3213
3214
3215status_t
3216FSCreateNewFolder(const entry_ref* ref)
3217{
3218	node_ref node;
3219	node.device = ref->device;
3220	node.node = ref->directory;
3221
3222	BDirectory dir(&node);
3223	status_t result = dir.InitCheck();
3224	if (result != B_OK)
3225		return result;
3226
3227	// ToDo: is that really necessary here?
3228	BString name(ref->name);
3229	FSMakeOriginalName(name, &dir, "-");
3230
3231	BDirectory newDir;
3232	result = dir.CreateDirectory(name.String(), &newDir);
3233	if (result != B_OK)
3234		return result;
3235
3236	BNodeInfo nodeInfo(&newDir);
3237	nodeInfo.SetType(B_DIR_MIMETYPE);
3238
3239	return result;
3240}
3241
3242
3243status_t
3244FSCreateNewFolderIn(const node_ref* dirNode, entry_ref* newRef,
3245	node_ref* newNode)
3246{
3247	BDirectory dir(dirNode);
3248	status_t result = dir.InitCheck();
3249	if (result == B_OK) {
3250		char name[B_FILE_NAME_LENGTH];
3251		strlcpy(name, B_TRANSLATE("New folder"), sizeof(name));
3252
3253		int fnum = 1;
3254		while (dir.Contains(name)) {
3255			// if base name already exists then add a number
3256			// TODO: move this logic to FSMakeOriginalName
3257			if (++fnum > 9)
3258				snprintf(name, sizeof(name), B_TRANSLATE("New folder%d"), fnum);
3259			else
3260				snprintf(name, sizeof(name), B_TRANSLATE("New folder %d"), fnum);
3261		}
3262
3263		BDirectory newDir;
3264		result = dir.CreateDirectory(name, &newDir);
3265		if (result == B_OK) {
3266			BEntry entry;
3267			newDir.GetEntry(&entry);
3268			entry.GetRef(newRef);
3269			entry.GetNodeRef(newNode);
3270
3271			BNodeInfo nodeInfo(&newDir);
3272			nodeInfo.SetType(B_DIR_MIMETYPE);
3273
3274			// add undo item
3275			NewFolderUndo undo(*newRef);
3276			return B_OK;
3277		}
3278	}
3279
3280	BAlert* alert = new BAlert("",
3281		B_TRANSLATE("Sorry, could not create a new folder."),
3282		B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3283	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3284	alert->Go();
3285
3286	return result;
3287}
3288
3289
3290ReadAttrResult
3291ReadAttr(const BNode* node, const char* hostAttrName,
3292	const char* foreignAttrName, type_code type, off_t offset, void* buffer,
3293	size_t length, void (*swapFunc)(void*), bool isForeign)
3294{
3295	if (!isForeign && node->ReadAttr(hostAttrName, type, offset, buffer,
3296			length) == (ssize_t)length) {
3297		return kReadAttrNativeOK;
3298	}
3299
3300	// PRINT(("trying %s\n", foreignAttrName));
3301	// try the other endianness
3302	if (node->ReadAttr(foreignAttrName, type, offset, buffer, length)
3303			!= (ssize_t)length) {
3304		return kReadAttrFailed;
3305	}
3306
3307	// PRINT(("got %s\n", foreignAttrName));
3308	if (!swapFunc)
3309		return kReadAttrForeignOK;
3310
3311	(swapFunc)(buffer);
3312		// run the endian swapper
3313
3314	return kReadAttrForeignOK;
3315}
3316
3317
3318ReadAttrResult
3319GetAttrInfo(const BNode* node, const char* hostAttrName,
3320	const char* foreignAttrName, type_code* type, size_t* size)
3321{
3322	attr_info info;
3323
3324	if (node->GetAttrInfo(hostAttrName, &info) == B_OK) {
3325		if (type)
3326			*type = info.type;
3327		if (size)
3328			*size = (size_t)info.size;
3329
3330		return kReadAttrNativeOK;
3331	}
3332
3333	if (node->GetAttrInfo(foreignAttrName, &info) == B_OK) {
3334		if (type)
3335			*type = info.type;
3336		if (size)
3337			*size = (size_t)info.size;
3338
3339		return kReadAttrForeignOK;
3340	}
3341	return kReadAttrFailed;
3342}
3343
3344
3345status_t
3346FSGetParentVirtualDirectoryAware(const BEntry& entry, entry_ref& _ref)
3347{
3348	node_ref nodeRef;
3349	if (entry.GetNodeRef(&nodeRef) == B_OK) {
3350		if (VirtualDirectoryManager* manager
3351				= VirtualDirectoryManager::Instance()) {
3352			AutoLocker<VirtualDirectoryManager> managerLocker(manager);
3353			if (manager->GetParentDirectoryDefinitionFile(nodeRef, _ref,
3354					nodeRef)) {
3355				return B_OK;
3356			}
3357		}
3358	}
3359
3360	status_t error;
3361	BDirectory parent;
3362	BEntry parentEntry;
3363	if ((error = entry.GetParent(&parent)) != B_OK
3364		|| (error = parent.GetEntry(&parentEntry)) != B_OK
3365		|| (error = parentEntry.GetRef(&_ref)) != B_OK) {
3366		return error;
3367	}
3368
3369	return B_OK;
3370}
3371
3372
3373status_t
3374FSGetParentVirtualDirectoryAware(const BEntry& entry, BEntry& _entry)
3375{
3376	node_ref nodeRef;
3377	if (entry.GetNodeRef(&nodeRef) == B_OK) {
3378		if (VirtualDirectoryManager* manager
3379				= VirtualDirectoryManager::Instance()) {
3380			AutoLocker<VirtualDirectoryManager> managerLocker(manager);
3381			entry_ref parentRef;
3382			if (manager->GetParentDirectoryDefinitionFile(nodeRef, parentRef,
3383					nodeRef)) {
3384				return _entry.SetTo(&parentRef);
3385			}
3386		}
3387	}
3388
3389	return entry.GetParent(&_entry);
3390}
3391
3392
3393status_t
3394FSGetParentVirtualDirectoryAware(const BEntry& entry, BNode& _node)
3395{
3396	entry_ref ref;
3397	status_t error = FSGetParentVirtualDirectoryAware(entry, ref);
3398	if (error == B_OK)
3399		error = _node.SetTo(&ref);
3400
3401	return error;
3402}
3403
3404
3405// launching code
3406
3407static status_t
3408TrackerOpenWith(const BMessage* refs)
3409{
3410	BMessage clone(*refs);
3411
3412	ASSERT(dynamic_cast<TTracker*>(be_app) != NULL);
3413	ASSERT(clone.what != 0);
3414
3415	clone.AddInt32("launchUsingSelector", 0);
3416	// runs the Open With window
3417	be_app->PostMessage(&clone);
3418
3419	return B_OK;
3420}
3421
3422
3423static void
3424AsynchLaunchBinder(void (*func)(const entry_ref*, const BMessage*, bool on),
3425	const entry_ref* appRef, const BMessage* refs, bool openWithOK)
3426{
3427	BMessage* task = new BMessage;
3428	task->AddPointer("function", (void*)func);
3429	task->AddMessage("refs", refs);
3430	task->AddBool("openWithOK", openWithOK);
3431	if (appRef != NULL)
3432		task->AddRef("appRef", appRef);
3433
3434	extern BLooper* gLaunchLooper;
3435	gLaunchLooper->PostMessage(task);
3436}
3437
3438
3439static bool
3440SniffIfGeneric(const entry_ref* ref)
3441{
3442	BNode node(ref);
3443	char type[B_MIME_TYPE_LENGTH];
3444	BNodeInfo info(&node);
3445	if (info.GetType(type) == B_OK
3446		&& strcasecmp(type, B_FILE_MIME_TYPE) != 0) {
3447		// already has a type and it's not octet stream
3448		return false;
3449	}
3450
3451	BPath path(ref);
3452	if (path.Path()) {
3453		// force a mimeset
3454		node.RemoveAttr(kAttrMIMEType);
3455		update_mime_info(path.Path(), 0, 1, 1);
3456	}
3457
3458	return true;
3459}
3460
3461
3462static void
3463SniffIfGeneric(const BMessage* refs)
3464{
3465	entry_ref ref;
3466	for (int32 index = 0; ; index++) {
3467		if (refs->FindRef("refs", index, &ref) != B_OK)
3468			break;
3469		SniffIfGeneric(&ref);
3470	}
3471}
3472
3473
3474static void
3475_TrackerLaunchAppWithDocuments(const entry_ref* appRef, const BMessage* refs,
3476	bool openWithOK)
3477{
3478	team_id team;
3479
3480	status_t error = B_ERROR;
3481	BString alertString;
3482
3483	for (int32 mimesetIt = 0; ; mimesetIt++) {
3484		error = be_roster->Launch(appRef, refs, &team);
3485		if (error == B_ALREADY_RUNNING)
3486			// app already running, not really an error
3487			error = B_OK;
3488
3489		if (error == B_OK)
3490			break;
3491
3492		if (mimesetIt > 0)
3493			break;
3494
3495		// failed to open, try mimesetting the refs and launching again
3496		SniffIfGeneric(refs);
3497	}
3498
3499	if (error == B_OK) {
3500		// close possible parent window, if specified
3501		const node_ref* nodeToClose = 0;
3502		ssize_t numBytes;
3503		if (refs != NULL && refs->FindData("nodeRefsToClose", B_RAW_TYPE,
3504				(const void**)&nodeToClose, &numBytes) == B_OK
3505			&& nodeToClose != NULL) {
3506			TTracker* tracker = dynamic_cast<TTracker*>(be_app);
3507			if (tracker != NULL)
3508				tracker->CloseParent(*nodeToClose);
3509		}
3510	} else {
3511		alertString.SetTo(B_TRANSLATE("Could not open \"%name\" (%error). "));
3512		alertString.ReplaceFirst("%name", appRef->name);
3513		alertString.ReplaceFirst("%error", strerror(error));
3514		if (refs != NULL && openWithOK && error != B_SHUTTING_DOWN) {
3515			alertString << B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3516			BAlert* alert = new BAlert("", alertString.String(),
3517				B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0,
3518				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3519			alert->SetShortcut(0, B_ESCAPE);
3520			if (alert->Go() == 1)
3521				error = TrackerOpenWith(refs);
3522		} else {
3523			BAlert* alert = new BAlert("", alertString.String(),
3524				B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
3525				B_WARNING_ALERT);
3526			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3527			alert->Go();
3528		}
3529	}
3530}
3531
3532
3533extern "C" char** environ;
3534
3535
3536static status_t
3537LoaderErrorDetails(const entry_ref* app, BString &details)
3538{
3539	BPath path;
3540	BEntry appEntry(app, true);
3541
3542	status_t result = appEntry.GetPath(&path);
3543	if (result != B_OK)
3544		return result;
3545
3546	char* argv[2] = { const_cast<char*>(path.Path()), 0};
3547
3548	port_id errorPort = create_port(1, "Tracker loader error");
3549
3550	// count environment variables
3551	int32 envCount = 0;
3552	while (environ[envCount] != NULL)
3553		envCount++;
3554
3555	char** flatArgs = NULL;
3556	size_t flatArgsSize;
3557	result = __flatten_process_args((const char**)argv, 1,
3558		environ, &envCount, argv[0], &flatArgs, &flatArgsSize);
3559	if (result != B_OK)
3560		return result;
3561
3562	result = _kern_load_image(flatArgs, flatArgsSize, 1, envCount,
3563		B_NORMAL_PRIORITY, B_WAIT_TILL_LOADED, errorPort, 0);
3564	if (result == B_OK) {
3565		// we weren't supposed to be able to start the application...
3566		return B_ERROR;
3567	}
3568
3569	// read error message from port and construct details string
3570
3571	ssize_t bufferSize;
3572
3573	do {
3574		bufferSize = port_buffer_size_etc(errorPort, B_RELATIVE_TIMEOUT, 0);
3575	} while (bufferSize == B_INTERRUPTED);
3576
3577	if (bufferSize <= B_OK) {
3578		delete_port(errorPort);
3579		return bufferSize;
3580	}
3581
3582	uint8* buffer = (uint8*)malloc(bufferSize);
3583	if (buffer == NULL) {
3584		delete_port(errorPort);
3585		return B_NO_MEMORY;
3586	}
3587
3588	bufferSize = read_port_etc(errorPort, NULL, buffer, bufferSize,
3589		B_RELATIVE_TIMEOUT, 0);
3590	delete_port(errorPort);
3591
3592	if (bufferSize < B_OK) {
3593		free(buffer);
3594		return bufferSize;
3595	}
3596
3597	BMessage message;
3598	result = message.Unflatten((const char*)buffer);
3599	free(buffer);
3600
3601	if (result != B_OK)
3602		return result;
3603
3604	int32 errorCode = B_ERROR;
3605	result = message.FindInt32("error", &errorCode);
3606	if (result != B_OK)
3607		return result;
3608
3609	const char* detailName = NULL;
3610	switch (errorCode) {
3611		case B_MISSING_LIBRARY:
3612			detailName = "missing library";
3613			break;
3614
3615		case B_MISSING_SYMBOL:
3616			detailName = "missing symbol";
3617			break;
3618	}
3619
3620	if (detailName == NULL)
3621		return B_ERROR;
3622
3623	const char* detail;
3624	for (int32 i = 0; message.FindString(detailName, i, &detail) == B_OK;
3625			i++) {
3626		if (i > 0)
3627			details += ", ";
3628		details += detail;
3629	}
3630
3631	return B_OK;
3632}
3633
3634
3635static void
3636_TrackerLaunchDocuments(const entry_ref*, const BMessage* refs,
3637	bool openWithOK)
3638{
3639	if (refs == NULL)
3640		return;
3641
3642	BMessage copyOfRefs(*refs);
3643
3644	entry_ref documentRef;
3645	if (copyOfRefs.FindRef("refs", &documentRef) != B_OK) {
3646		// nothing to launch, we are done
3647		return;
3648	}
3649
3650	status_t error = B_ERROR;
3651	entry_ref app;
3652	BMessage* refsToPass = NULL;
3653	BString alertString;
3654	const char* alternative = 0;
3655
3656	for (int32 mimesetIt = 0; ; mimesetIt++) {
3657		alertString = "";
3658		error = be_roster->FindApp(&documentRef, &app);
3659
3660		if (error != B_OK && mimesetIt == 0) {
3661			SniffIfGeneric(&copyOfRefs);
3662			continue;
3663		}
3664
3665		if (error != B_OK) {
3666			alertString.SetTo(B_TRANSLATE("Could not find an application to "
3667				"open \"%name\" (%error). "));
3668			alertString.ReplaceFirst("%name", documentRef.name);
3669			alertString.ReplaceFirst("%error", strerror(error));
3670			if (openWithOK)
3671				alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr);
3672
3673			break;
3674		} else {
3675			BEntry appEntry(&app, true);
3676			for (int32 index = 0;;) {
3677				// remove the app itself from the refs received so we don't
3678				// try to open ourselves
3679				entry_ref ref;
3680				if (copyOfRefs.FindRef("refs", index, &ref) != B_OK)
3681					break;
3682
3683				// deal with symlinks properly
3684				BEntry documentEntry(&ref, true);
3685				if (appEntry == documentEntry) {
3686					PRINT(("stripping %s, app %s \n", ref.name, app.name));
3687					copyOfRefs.RemoveData("refs", index);
3688				} else {
3689					PRINT(("leaving %s, app %s  \n", ref.name, app.name));
3690					index++;
3691				}
3692			}
3693
3694			refsToPass = CountRefs(&copyOfRefs) > 0 ? &copyOfRefs: 0;
3695			team_id team;
3696			error = be_roster->Launch(&app, refsToPass, &team);
3697			if (error == B_ALREADY_RUNNING)
3698				// app already running, not really an error
3699				error = B_OK;
3700			if (error == B_OK || mimesetIt != 0)
3701				break;
3702			if (error == B_LAUNCH_FAILED_EXECUTABLE) {
3703				BVolume volume(documentRef.device);
3704				if (volume.IsReadOnly()) {
3705					BMimeType type;
3706					error = BMimeType::GuessMimeType(&documentRef, &type);
3707					if (error != B_OK)
3708						break;
3709					error = be_roster->FindApp(type.Type(), &app);
3710					if (error != B_OK)
3711						break;
3712					error = be_roster->Launch(&app, refs, &team);
3713					if (error == B_ALREADY_RUNNING)
3714						// app already running, not really an error
3715						error = B_OK;
3716					if (error == B_OK || mimesetIt != 0)
3717						break;
3718				}
3719			}
3720
3721			SniffIfGeneric(&copyOfRefs);
3722		}
3723	}
3724
3725	if (error != B_OK && alertString.Length() == 0) {
3726		BString loaderErrorString;
3727		bool openedDocuments = true;
3728
3729		if (!refsToPass) {
3730			// we just double clicked the app itself, do not offer to
3731			// find a handling app
3732			openWithOK = false;
3733			openedDocuments = false;
3734		}
3735		if (error == B_UNKNOWN_EXECUTABLE && !refsToPass) {
3736			// We know it's an executable, but something unsupported
3737			alertString.SetTo(B_TRANSLATE("\"%name\" is an unsupported "
3738				"executable."));
3739			alertString.ReplaceFirst("%name", app.name);
3740		} else if (error == B_LEGACY_EXECUTABLE && !refsToPass) {
3741			// For the moment, this marks an old R3 binary, we may want to
3742			// extend it to gcc2 binaries someday post R1
3743			alertString.SetTo(B_TRANSLATE("\"%name\" is a legacy executable. "
3744				"Please obtain an updated version or recompile "
3745				"the application."));
3746			alertString.ReplaceFirst("%name", app.name);
3747		} else if (error == B_LAUNCH_FAILED_EXECUTABLE && !refsToPass) {
3748			alertString.SetTo(B_TRANSLATE("Could not open \"%name\". "
3749				"The file is mistakenly marked as executable. "));
3750			alertString.ReplaceFirst("%name", app.name);
3751
3752			if (!openWithOK) {
3753				// offer the possibility to change the permissions
3754
3755				alertString << B_TRANSLATE("\nShould this be fixed?");
3756				BAlert* alert = new BAlert("", alertString.String(),
3757					B_TRANSLATE("Cancel"), B_TRANSLATE("Proceed"), 0,
3758					B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3759				alert->SetShortcut(0, B_ESCAPE);
3760				if (alert->Go() == 1) {
3761					BEntry entry(&documentRef);
3762					mode_t permissions;
3763
3764					error = entry.GetPermissions(&permissions);
3765					if (error == B_OK) {
3766						error = entry.SetPermissions(permissions
3767							& ~(S_IXUSR | S_IXGRP | S_IXOTH));
3768					}
3769					if (error == B_OK) {
3770						// we updated the permissions, so let's try again
3771						_TrackerLaunchDocuments(NULL, refs, false);
3772						return;
3773					} else {
3774						alertString.SetTo(B_TRANSLATE("Could not update "
3775							"permissions of file \"%name\". %error"));
3776						alertString.ReplaceFirst("%name", app.name);
3777						alertString.ReplaceFirst("%error", strerror(error));
3778					}
3779				} else
3780					return;
3781			}
3782
3783			alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr);
3784		} else if (error == B_LAUNCH_FAILED_APP_IN_TRASH) {
3785			alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3786				"because application \"%app\" is in the Trash. "));
3787			alertString.ReplaceFirst("%document", documentRef.name);
3788			alertString.ReplaceFirst("%app", app.name);
3789			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3790		} else if (error == B_LAUNCH_FAILED_APP_NOT_FOUND) {
3791			alertString.SetTo(
3792				B_TRANSLATE("Could not open \"%name\" (%error). "));
3793			alertString.ReplaceFirst("%name", documentRef.name);
3794			alertString.ReplaceFirst("%error", strerror(error));
3795			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3796		} else if (error == B_MISSING_SYMBOL
3797			&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3798			if (openedDocuments) {
3799				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3800					"with application \"%app\" (Missing symbol: %symbol). "
3801					"\n"));
3802				alertString.ReplaceFirst("%document", documentRef.name);
3803				alertString.ReplaceFirst("%app", app.name);
3804				alertString.ReplaceFirst("%symbol",
3805					loaderErrorString.String());
3806			} else {
3807				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3808					"(Missing symbol: %symbol). \n"));
3809				alertString.ReplaceFirst("%document", documentRef.name);
3810				alertString.ReplaceFirst("%symbol",
3811					loaderErrorString.String());
3812			}
3813			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3814		} else if (error == B_MISSING_LIBRARY
3815			&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3816			if (openedDocuments) {
3817				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3818					"with application \"%app\" (Missing libraries: %library). "
3819					"\n"));
3820				alertString.ReplaceFirst("%document", documentRef.name);
3821				alertString.ReplaceFirst("%app", app.name);
3822				alertString.ReplaceFirst("%library",
3823					loaderErrorString.String());
3824			} else {
3825				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3826					"(Missing libraries: %library). \n"));
3827				alertString.ReplaceFirst("%document", documentRef.name);
3828				alertString.ReplaceFirst("%library",
3829					loaderErrorString.String());
3830			}
3831			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3832		} else {
3833			alertString.SetTo(B_TRANSLATE("Could not open \"%document\" with "
3834				"application \"%app\" (%error). "));
3835				alertString.ReplaceFirst("%document", documentRef.name);
3836				alertString.ReplaceFirst("%app", app.name);
3837				alertString.ReplaceFirst("%error", strerror(error));
3838			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3839		}
3840	}
3841
3842	if (error != B_OK) {
3843		if (openWithOK) {
3844			ASSERT(alternative);
3845			alertString << alternative;
3846			BAlert* alert = new BAlert("", alertString.String(),
3847				B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0,
3848				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3849			alert->SetShortcut(0, B_ESCAPE);
3850			if (alert->Go() == 1)
3851				error = TrackerOpenWith(refs);
3852		} else {
3853			BAlert* alert = new BAlert("", alertString.String(),
3854				B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
3855				B_WARNING_ALERT);
3856			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3857			alert->Go();
3858		}
3859	}
3860}
3861
3862// the following three calls don't return any reasonable error codes,
3863// should fix that, making them void
3864
3865status_t
3866TrackerLaunch(const entry_ref* appRef, const BMessage* refs, bool async,
3867	bool openWithOK)
3868{
3869	if (!async)
3870		_TrackerLaunchAppWithDocuments(appRef, refs, openWithOK);
3871	else {
3872		AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, refs,
3873			openWithOK);
3874	}
3875
3876	return B_OK;
3877}
3878
3879status_t
3880TrackerLaunch(const entry_ref* appRef, bool async)
3881{
3882	if (!async)
3883		_TrackerLaunchAppWithDocuments(appRef, NULL, false);
3884	else
3885		AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, 0, false);
3886
3887	return B_OK;
3888}
3889
3890status_t
3891TrackerLaunch(const BMessage* refs, bool async, bool openWithOK)
3892{
3893	if (!async)
3894		_TrackerLaunchDocuments(NULL, refs, openWithOK);
3895	else
3896		AsynchLaunchBinder(&_TrackerLaunchDocuments, NULL, refs, openWithOK);
3897
3898	return B_OK;
3899}
3900
3901
3902// external launch calls; need to be robust, work if Tracker is not running
3903
3904
3905#if !B_BEOS_VERSION_DANO
3906_IMPEXP_TRACKER
3907#endif
3908status_t
3909FSLaunchItem(const entry_ref* application, const BMessage* refsReceived,
3910	bool async, bool openWithOK)
3911{
3912	return TrackerLaunch(application, refsReceived, async, openWithOK);
3913}
3914
3915
3916#if !B_BEOS_VERSION_DANO
3917_IMPEXP_TRACKER
3918#endif
3919status_t
3920FSOpenWith(BMessage* listOfRefs)
3921{
3922	status_t result = B_ERROR;
3923	listOfRefs->what = B_REFS_RECEIVED;
3924
3925	if (dynamic_cast<TTracker*>(be_app) != NULL)
3926		result = TrackerOpenWith(listOfRefs);
3927	else
3928		ASSERT(!"not yet implemented");
3929
3930	return result;
3931}
3932
3933
3934// legacy calls, need for compatibility
3935
3936
3937void
3938FSOpenWithDocuments(const entry_ref* executable, BMessage* documents)
3939{
3940	TrackerLaunch(executable, documents, true);
3941	delete documents;
3942}
3943
3944
3945status_t
3946FSLaunchUsing(const entry_ref* ref, BMessage* listOfRefs)
3947{
3948	BMessage temp(B_REFS_RECEIVED);
3949	if (listOfRefs == NULL) {
3950		ASSERT(ref != NULL);
3951		temp.AddRef("refs", ref);
3952		listOfRefs = &temp;
3953	}
3954	FSOpenWith(listOfRefs);
3955
3956	return B_OK;
3957}
3958
3959
3960status_t
3961FSLaunchItem(const entry_ref* appRef, BMessage* refs, int32, bool async)
3962{
3963	if (refs != NULL)
3964		refs->what = B_REFS_RECEIVED;
3965
3966	status_t result = TrackerLaunch(appRef, refs, async, true);
3967	delete refs;
3968
3969	return result;
3970}
3971
3972
3973void
3974FSLaunchItem(const entry_ref* appRef, BMessage* refs, int32 workspace)
3975{
3976	FSLaunchItem(appRef, refs, workspace, true);
3977}
3978
3979
3980// Get the original path of an entry in the trash
3981status_t
3982FSGetOriginalPath(BEntry* entry, BPath* result)
3983{
3984	status_t err;
3985	entry_ref ref;
3986	err = entry->GetRef(&ref);
3987	if (err != B_OK)
3988		return err;
3989
3990	// Only call the routine for entries in the trash
3991	if (!FSInTrashDir(&ref))
3992		return B_ERROR;
3993
3994	BNode node(entry);
3995	BString originalPath;
3996	if (node.ReadAttrString(kAttrOriginalPath, &originalPath) == B_OK) {
3997		// We're in luck, the entry has the original path in an attribute
3998		err = result->SetTo(originalPath.String());
3999		return err;
4000	}
4001
4002	// Iterate the parent directories to find one with
4003	// the original path attribute
4004	BEntry parent(*entry);
4005	err = parent.InitCheck();
4006	if (err != B_OK)
4007		return err;
4008
4009	// walk up the directory structure until we find a node
4010	// with original path attribute
4011	do {
4012		// move to the parent of this node
4013		err = parent.GetParent(&parent);
4014		if (err != B_OK)
4015			return err;
4016
4017		// return if we are at the root of the trash
4018		if (FSIsTrashDir(&parent))
4019			return B_ENTRY_NOT_FOUND;
4020
4021		// get the parent as a node
4022		err = node.SetTo(&parent);
4023		if (err != B_OK)
4024			return err;
4025	} while (node.ReadAttrString(kAttrOriginalPath, &originalPath) != B_OK);
4026
4027	// Found the attribute, figure out there this file
4028	// used to live, based on the successfully-read attribute
4029	err = result->SetTo(originalPath.String());
4030	if (err != B_OK)
4031		return err;
4032
4033	BPath path, pathParent;
4034	err = parent.GetPath(&pathParent);
4035	if (err != B_OK)
4036		return err;
4037
4038	err = entry->GetPath(&path);
4039	if (err != B_OK)
4040		return err;
4041
4042	result->Append(path.Path() + strlen(pathParent.Path()) + 1);
4043		// compute the new path by appending the offset of
4044		// the item we are locating, to the original path
4045		// of the parent
4046
4047	return B_OK;
4048}
4049
4050
4051directory_which
4052WellKnowEntryList::Match(const node_ref* node)
4053{
4054	const WellKnownEntry* result = MatchEntry(node);
4055	if (result != NULL)
4056		return result->which;
4057
4058	return (directory_which)-1;
4059}
4060
4061
4062const WellKnowEntryList::WellKnownEntry*
4063WellKnowEntryList::MatchEntry(const node_ref* node)
4064{
4065	if (self == NULL)
4066		self = new WellKnowEntryList();
4067
4068	return self->MatchEntryCommon(node);
4069}
4070
4071
4072const WellKnowEntryList::WellKnownEntry*
4073WellKnowEntryList::MatchEntryCommon(const node_ref* node)
4074{
4075	uint32 count = entries.size();
4076	for (uint32 index = 0; index < count; index++) {
4077		if (*node == entries[index].node)
4078			return &entries[index];
4079	}
4080
4081	return NULL;
4082}
4083
4084
4085void
4086WellKnowEntryList::Quit()
4087{
4088	delete self;
4089	self = NULL;
4090}
4091
4092
4093void
4094WellKnowEntryList::AddOne(directory_which which, const char* name)
4095{
4096	BPath path;
4097	if (find_directory(which, &path, true) != B_OK)
4098		return;
4099
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, directory_which base,
4111	const char* extra, const char* name)
4112{
4113	BPath path;
4114	if (find_directory(base, &path, true) != B_OK)
4115		return;
4116
4117	path.Append(extra);
4118	BEntry entry(path.Path(), true);
4119	node_ref node;
4120	if (entry.GetNodeRef(&node) != B_OK)
4121		return;
4122
4123	entries.push_back(WellKnownEntry(&node, which, name));
4124}
4125
4126
4127void
4128WellKnowEntryList::AddOne(directory_which which, const char* path,
4129	const char* name)
4130{
4131	BEntry entry(path, true);
4132	node_ref node;
4133	if (entry.GetNodeRef(&node) != B_OK)
4134		return;
4135
4136	entries.push_back(WellKnownEntry(&node, which, name));
4137}
4138
4139
4140WellKnowEntryList::WellKnowEntryList()
4141{
4142	AddOne(B_SYSTEM_DIRECTORY, "system");
4143	AddOne((directory_which)B_BOOT_DISK, "/boot", "boot");
4144	AddOne(B_USER_DIRECTORY, "home");
4145
4146	AddOne(B_BEOS_FONTS_DIRECTORY, "fonts");
4147	AddOne(B_USER_FONTS_DIRECTORY, "fonts");
4148
4149	AddOne(B_BEOS_APPS_DIRECTORY, "apps");
4150	AddOne(B_APPS_DIRECTORY, "apps");
4151	AddOne((directory_which)B_USER_DESKBAR_APPS_DIRECTORY,
4152		B_USER_DESKBAR_DIRECTORY, "Applications", "apps");
4153
4154	AddOne(B_BEOS_PREFERENCES_DIRECTORY, "preferences");
4155	AddOne(B_PREFERENCES_DIRECTORY, "preferences");
4156	AddOne((directory_which)B_USER_DESKBAR_PREFERENCES_DIRECTORY,
4157		B_USER_DESKBAR_DIRECTORY, "Preferences", "preferences");
4158
4159	AddOne((directory_which)B_USER_MAIL_DIRECTORY, B_USER_DIRECTORY, "mail",
4160		"mail");
4161
4162	AddOne((directory_which)B_USER_QUERIES_DIRECTORY, B_USER_DIRECTORY,
4163		"queries", "queries");
4164
4165	AddOne(B_SYSTEM_DEVELOP_DIRECTORY, "develop");
4166	AddOne((directory_which)B_USER_DESKBAR_DEVELOP_DIRECTORY,
4167		B_USER_DESKBAR_DIRECTORY, "Development", "develop");
4168
4169	AddOne(B_USER_CONFIG_DIRECTORY, "config");
4170
4171	AddOne((directory_which)B_USER_PEOPLE_DIRECTORY, B_USER_DIRECTORY,
4172		"people", "people");
4173
4174	AddOne((directory_which)B_USER_DOWNLOADS_DIRECTORY, B_USER_DIRECTORY,
4175		"downloads", "downloads");
4176}
4177
4178WellKnowEntryList* WellKnowEntryList::self = NULL;
4179
4180} // namespace BPrivate
4181