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