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