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