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