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