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