xref: /haiku/src/kits/tracker/FSUtils.cpp (revision eea5774f46bba925156498abf9cb1a1165647bf7)
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 : NULL);
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 	BString copySuffix(B_TRANSLATE_COMMENT("copy", "filename copy"));
2498 	size_t suffixLength = copySuffix.Length();
2499 	if (suffix == NULL)
2500 		suffix = copySuffix;
2501 
2502 	// Determine if we're copying a 'copy'. This algorithm isn't perfect.
2503 	// If you're copying a file whose REAL name ends with 'copy' then
2504 	// this method will return "<filename> 1", not "<filename> copy"
2505 
2506 	// However, it will correctly handle file that contain 'copy'
2507 	// elsewhere in their name.
2508 
2509 	bool copycopy = false;		// are we copying a copy?
2510 	int32 len = (int32)strlen(name);
2511 	char* p = name + len - 1;	// get pointer to end of name
2512 
2513 	// eat up optional numbers (if we're copying "<filename> copy 34")
2514 	while ((p > name) && isdigit(*p))
2515 		p--;
2516 
2517 	// eat up optional spaces
2518 	while ((p > name) && isspace(*p))
2519 		p--;
2520 
2521 	// now look for the phrase " copy"
2522 	if (p > name) {
2523 		// p points to the last char of the word. For example, 'y' in 'copy'
2524 
2525 		if ((p - suffixLength > name)
2526 			&& (strncmp(p - suffixLength, suffix, suffixLength + 1) == 0)) {
2527 			// we found 'copy' in the right place.
2528 			// so truncate after 'copy'
2529 			*(p + 1) = '\0';
2530 			copycopy = true;
2531 
2532 			// save the 'root' name of the file, for possible later use.
2533 			// that is copy everything but trailing " copy". Need to
2534 			// NULL terminate after copy
2535 			strncpy(root, name, (uint32)((p - name) - suffixLength));
2536 			root[(p - name) - suffixLength] = '\0';
2537 		}
2538 	}
2539 
2540 	if (!copycopy) {
2541 		// The name can't be longer than B_FILE_NAME_LENGTH.
2542 		// The algorithm adds a localized " copy XX" to the name.
2543 		// That's the number of characters of "copy" + 4 (spaces + "XX").
2544 		// B_FILE_NAME_LENGTH already accounts for NULL termination so we
2545 		// don't need to save an extra char at the end.
2546 		if (strlen(name) > B_FILE_NAME_LENGTH - (suffixLength + 4)) {
2547 			// name is too long - truncate it!
2548 			name[B_FILE_NAME_LENGTH - (suffixLength + 4)] = '\0';
2549 		}
2550 
2551 		strlcpy(root, name, sizeof(root));
2552 			// save root name
2553 		strlcat(name, suffix, B_FILE_NAME_LENGTH);
2554 	}
2555 
2556 	strlcpy(copybase, name, sizeof(copybase));
2557 
2558 	// if name already exists then add a number
2559 	fnum = 1;
2560 	strlcpy(tempName, name, sizeof(tempName));
2561 	while (destDir->Contains(tempName)) {
2562 		snprintf(tempName, sizeof(tempName), "%s %" B_PRId32, copybase,
2563 			++fnum);
2564 
2565 		if (strlen(tempName) > (B_FILE_NAME_LENGTH - 1)) {
2566 			// The name has grown too long. Maybe we just went from
2567 			// "<filename> copy 9" to "<filename> copy 10" and that extra
2568 			// character was too much. The solution is to further
2569 			// truncate the 'root' name and continue.
2570 			// ??? should we reset fnum or not ???
2571 			root[strlen(root) - 1] = '\0';
2572 			snprintf(tempName, sizeof(tempName), "%s%s %" B_PRId32, root,
2573 				suffix, fnum);
2574 		}
2575 	}
2576 
2577 	strlcpy(name, tempName, B_FILE_NAME_LENGTH);
2578 }
2579 
2580 
2581 status_t
2582 FSRecursiveCalcSize(BInfoWindow* window, CopyLoopControl* loopControl,
2583 	BDirectory* dir, off_t* _runningSize, int32* _fileCount, int32* _dirCount)
2584 {
2585 	dir->Rewind();
2586 	BEntry entry;
2587 	while (dir->GetNextEntry(&entry) == B_OK) {
2588 		// be sure window hasn't closed
2589 		if (window && window->StopCalc())
2590 			return B_OK;
2591 
2592 		if (loopControl->CheckUserCanceled())
2593 			return kUserCanceled;
2594 
2595 		StatStruct statbuf;
2596 		status_t status = entry.GetStat(&statbuf);
2597 		if (status != B_OK)
2598 			return status;
2599 
2600 		(*_runningSize) += statbuf.st_blocks * 512;
2601 
2602 		if (S_ISDIR(statbuf.st_mode)) {
2603 			BDirectory subdir(&entry);
2604 			(*_dirCount)++;
2605 			status = FSRecursiveCalcSize(window, loopControl, &subdir,
2606 				_runningSize, _fileCount, _dirCount);
2607 			if (status != B_OK)
2608 				return status;
2609 		} else
2610 			(*_fileCount)++;
2611 	}
2612 	return B_OK;
2613 }
2614 
2615 
2616 status_t
2617 CalcItemsAndSize(CopyLoopControl* loopControl,
2618 	BObjectList<entry_ref>* refList, ssize_t blockSize, int32* totalCount,
2619 	off_t* totalSize)
2620 {
2621 	int32 fileCount = 0;
2622 	int32 dirCount = 0;
2623 
2624 	// check block size for sanity
2625 	if (blockSize < 0) {
2626 		// This would point at an error to retrieve the block size from
2627 		// the target volume. The code below cannot be used, it is only
2628 		// meant to get the block size when item operations happen on
2629 		// the source volume.
2630 		blockSize = 2048;
2631 	} else if (blockSize < 1024) {
2632 		blockSize = 1024;
2633 		if (entry_ref* ref = refList->ItemAt(0)) {
2634 			// TODO: This assumes all entries in the list share the same
2635 			// volume...
2636 			BVolume volume(ref->device);
2637 			if (volume.InitCheck() == B_OK)
2638 				blockSize = volume.BlockSize();
2639 		}
2640 	}
2641 	// File systems like ReiserFS may advertize a large block size, but
2642 	// stuff is still packed into blocks, so clamp maximum block size.
2643 	if (blockSize > 8192)
2644 		blockSize = 8192;
2645 
2646 	int32 num_items = refList->CountItems();
2647 	for (int32 i = 0; i < num_items; i++) {
2648 		entry_ref* ref = refList->ItemAt(i);
2649 		BEntry entry(ref);
2650 		StatStruct statbuf;
2651 		entry.GetStat(&statbuf);
2652 
2653 		if (loopControl->CheckUserCanceled())
2654 			return kUserCanceled;
2655 
2656 		if (S_ISDIR(statbuf.st_mode)) {
2657 			BDirectory dir(&entry);
2658 			dirCount++;
2659 			(*totalSize) += blockSize;
2660 			status_t result = FSRecursiveCalcSize(NULL, loopControl, &dir,
2661 				totalSize, &fileCount, &dirCount);
2662 			if (result != B_OK)
2663 				return result;
2664 		} else {
2665 			fileCount++;
2666 			(*totalSize) += statbuf.st_size + blockSize;
2667 		}
2668 	}
2669 
2670 	*totalCount += (fileCount + dirCount);
2671 	return B_OK;
2672 }
2673 
2674 
2675 status_t
2676 FSGetTrashDir(BDirectory* trashDir, dev_t dev)
2677 {
2678 	if (trashDir == NULL)
2679 		return B_BAD_VALUE;
2680 
2681 	BVolume volume(dev);
2682 	status_t result = volume.InitCheck();
2683 	if (result != B_OK)
2684 		return result;
2685 
2686 	BPath path;
2687 	result = find_directory(B_TRASH_DIRECTORY, &path, false, &volume);
2688 	if (result != B_OK)
2689 		return result;
2690 
2691 	result = trashDir->SetTo(path.Path());
2692 	if (result != B_OK) {
2693 		// Trash directory does not exist yet, create it.
2694 		result = create_directory(path.Path(), 0755);
2695 		if (result != B_OK)
2696 			return result;
2697 
2698 		result = trashDir->SetTo(path.Path());
2699 		if (result != B_OK)
2700 			return result;
2701 
2702 		// make Trash directory invisible
2703 		StatStruct sbuf;
2704 		trashDir->GetStat(&sbuf);
2705 
2706 		PoseInfo poseInfo;
2707 		poseInfo.fInvisible = true;
2708 		poseInfo.fInitedDirectory = sbuf.st_ino;
2709 		trashDir->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
2710 			sizeof(PoseInfo));
2711 	}
2712 
2713 	// set Trash icons (if they haven't already been set)
2714 	attr_info attrInfo;
2715 	size_t size;
2716 	const void* data;
2717 	if (trashDir->GetAttrInfo(kAttrLargeIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2718 		data = GetTrackerResources()->LoadResource('ICON', R_TrashIcon, &size);
2719 		if (data != NULL)
2720 			trashDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
2721 	}
2722 
2723 	if (trashDir->GetAttrInfo(kAttrMiniIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2724 		data = GetTrackerResources()->LoadResource('MICN', R_TrashIcon, &size);
2725 		if (data != NULL)
2726 			trashDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
2727 	}
2728 
2729 	if (trashDir->GetAttrInfo(kAttrIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2730 		data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE,
2731 			R_TrashIcon, &size);
2732 		if (data != NULL)
2733 			trashDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
2734 	}
2735 
2736 	return B_OK;
2737 }
2738 
2739 
2740 status_t
2741 FSGetDeskDir(BDirectory* deskDir)
2742 {
2743 	if (deskDir == NULL)
2744 		return B_BAD_VALUE;
2745 
2746 	BPath path;
2747 	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true);
2748 	if (result != B_OK)
2749 		return result;
2750 
2751 	result = deskDir->SetTo(path.Path());
2752 	if (result != B_OK)
2753 		return result;
2754 
2755 	// set Desktop icons (if they haven't already been set)
2756 	attr_info attrInfo;
2757 	size_t size;
2758 	const void* data;
2759 	if (deskDir->GetAttrInfo(kAttrLargeIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2760 		data = GetTrackerResources()->LoadResource('ICON', R_DeskIcon, &size);
2761 		if (data != NULL)
2762 			deskDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
2763 	}
2764 
2765 	if (deskDir->GetAttrInfo(kAttrMiniIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2766 		data = GetTrackerResources()->LoadResource('MICN', R_DeskIcon, &size);
2767 		if (data != NULL)
2768 			deskDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
2769 	}
2770 
2771 	if (deskDir->GetAttrInfo(kAttrIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2772 		data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE,
2773 			R_DeskIcon, &size);
2774 		if (data != NULL)
2775 			deskDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
2776 	}
2777 
2778 	return B_OK;
2779 }
2780 
2781 
2782 status_t
2783 FSGetBootDeskDir(BDirectory* deskDir)
2784 {
2785 	BVolume bootVolume;
2786 	BVolumeRoster().GetBootVolume(&bootVolume);
2787 	BPath path;
2788 
2789 	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true,
2790 		&bootVolume);
2791 	if (result != B_OK)
2792 		return result;
2793 
2794 	return deskDir->SetTo(path.Path());
2795 }
2796 
2797 
2798 static bool
2799 FSIsDirFlavor(const BEntry* entry, directory_which directoryType)
2800 {
2801 	StatStruct dir_stat;
2802 	StatStruct entry_stat;
2803 	BVolume volume;
2804 	BPath path;
2805 
2806 	if (entry->GetStat(&entry_stat) != B_OK)
2807 		return false;
2808 
2809 	if (volume.SetTo(entry_stat.st_dev) != B_OK)
2810 		return false;
2811 
2812 	if (find_directory(directoryType, &path, false, &volume) != B_OK)
2813 		return false;
2814 
2815 	stat(path.Path(), &dir_stat);
2816 
2817 	return dir_stat.st_ino == entry_stat.st_ino
2818 		&& dir_stat.st_dev == entry_stat.st_dev;
2819 }
2820 
2821 
2822 bool
2823 FSIsPrintersDir(const BEntry* entry)
2824 {
2825 	return FSIsDirFlavor(entry, B_USER_PRINTERS_DIRECTORY);
2826 }
2827 
2828 
2829 bool
2830 FSIsTrashDir(const BEntry* entry)
2831 {
2832 	return FSIsDirFlavor(entry, B_TRASH_DIRECTORY);
2833 }
2834 
2835 
2836 bool
2837 FSIsDeskDir(const BEntry* entry)
2838 {
2839 	BPath path;
2840 	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true);
2841 	if (result != B_OK)
2842 		return false;
2843 
2844 	BEntry entryToCompare(path.Path());
2845 	return entryToCompare == *entry;
2846 }
2847 
2848 
2849 bool
2850 FSIsHomeDir(const BEntry* entry)
2851 {
2852 	return FSIsDirFlavor(entry, B_USER_DIRECTORY);
2853 }
2854 
2855 
2856 bool
2857 FSIsRootDir(const BEntry* entry)
2858 {
2859 	BPath path;
2860 	if (entry->InitCheck() != B_OK || entry->GetPath(&path) != B_OK)
2861 		return false;
2862 
2863 	return strcmp(path.Path(), "/") == 0;
2864 }
2865 
2866 
2867 bool
2868 DirectoryMatchesOrContains(const BEntry* entry, directory_which which)
2869 {
2870 	BPath path;
2871 	if (find_directory(which, &path, false, NULL) != B_OK)
2872 		return false;
2873 
2874 	BEntry dirEntry(path.Path());
2875 	if (dirEntry.InitCheck() != B_OK)
2876 		return false;
2877 
2878 	if (dirEntry == *entry)
2879 		// root level match
2880 		return true;
2881 
2882 	BDirectory dir(&dirEntry);
2883 	return dir.Contains(entry);
2884 }
2885 
2886 
2887 bool
2888 DirectoryMatchesOrContains(const BEntry* entry, const char* additionalPath,
2889 	directory_which which)
2890 {
2891 	BPath path;
2892 	if (find_directory(which, &path, false, NULL) != B_OK)
2893 		return false;
2894 
2895 	path.Append(additionalPath);
2896 	BEntry dirEntry(path.Path());
2897 	if (dirEntry.InitCheck() != B_OK)
2898 		return false;
2899 
2900 	if (dirEntry == *entry)
2901 		// root level match
2902 		return true;
2903 
2904 	BDirectory dir(&dirEntry);
2905 	return dir.Contains(entry);
2906 }
2907 
2908 
2909 bool
2910 DirectoryMatches(const BEntry* entry, directory_which which)
2911 {
2912 	BPath path;
2913 	if (find_directory(which, &path, false, NULL) != B_OK)
2914 		return false;
2915 
2916 	BEntry dirEntry(path.Path());
2917 	if (dirEntry.InitCheck() != B_OK)
2918 		return false;
2919 
2920 	return dirEntry == *entry;
2921 }
2922 
2923 
2924 bool
2925 DirectoryMatches(const BEntry* entry, const char* additionalPath,
2926 	directory_which which)
2927 {
2928 	BPath path;
2929 	if (find_directory(which, &path, false, NULL) != B_OK)
2930 		return false;
2931 
2932 	path.Append(additionalPath);
2933 	BEntry dirEntry(path.Path());
2934 	if (dirEntry.InitCheck() != B_OK)
2935 		return false;
2936 
2937 	return dirEntry == *entry;
2938 }
2939 
2940 
2941 extern status_t
2942 FSFindTrackerSettingsDir(BPath* path, bool autoCreate)
2943 {
2944 	status_t result = find_directory(B_USER_SETTINGS_DIRECTORY, path,
2945 		autoCreate);
2946 	if (result != B_OK)
2947 		return result;
2948 
2949 	path->Append("Tracker");
2950 
2951 	return mkdir(path->Path(), 0777) ? B_OK : errno;
2952 }
2953 
2954 
2955 bool
2956 FSInTrashDir(const entry_ref* ref)
2957 {
2958 	BEntry entry(ref);
2959 	if (entry.InitCheck() != B_OK)
2960 		return false;
2961 
2962 	BDirectory trashDir;
2963 	if (FSGetTrashDir(&trashDir, ref->device) != B_OK)
2964 		return false;
2965 
2966 	return trashDir.Contains(&entry);
2967 }
2968 
2969 
2970 void
2971 FSEmptyTrash()
2972 {
2973 	if (find_thread("_tracker_empty_trash_") != B_OK) {
2974 		resume_thread(spawn_thread(empty_trash, "_tracker_empty_trash_",
2975 			B_NORMAL_PRIORITY, NULL));
2976 	}
2977 }
2978 
2979 
2980 status_t
2981 empty_trash(void*)
2982 {
2983 	// empty trash on all mounted volumes
2984 	status_t status = B_OK;
2985 
2986 	TrackerCopyLoopControl loopControl(kTrashState);
2987 
2988 	// calculate the sum total of all items on all volumes in trash
2989 	BObjectList<entry_ref> srcList;
2990 	int32 totalCount = 0;
2991 	off_t totalSize = 0;
2992 
2993 	BVolumeRoster volumeRoster;
2994 	BVolume volume;
2995 	while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2996 		if (volume.IsReadOnly() || !volume.IsPersistent())
2997 			continue;
2998 
2999 		BDirectory trashDirectory;
3000 		if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
3001 			continue;
3002 
3003 		BEntry entry;
3004 		trashDirectory.GetEntry(&entry);
3005 
3006 		entry_ref ref;
3007 		entry.GetRef(&ref);
3008 		srcList.AddItem(&ref);
3009 		status = CalcItemsAndSize(&loopControl, &srcList, volume.BlockSize(),
3010 			&totalCount, &totalSize);
3011 		if (status != B_OK)
3012 			break;
3013 
3014 		srcList.MakeEmpty();
3015 
3016 		// don't count trash directory itself
3017 		totalCount--;
3018 	}
3019 
3020 	if (status == B_OK) {
3021 		loopControl.Init(totalCount, totalCount);
3022 
3023 		volumeRoster.Rewind();
3024 		while (volumeRoster.GetNextVolume(&volume) == B_OK) {
3025 			if (volume.IsReadOnly() || !volume.IsPersistent())
3026 				continue;
3027 
3028 			BDirectory trashDirectory;
3029 			if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
3030 				continue;
3031 
3032 			BEntry entry;
3033 			trashDirectory.GetEntry(&entry);
3034 			status = FSDeleteFolder(&entry, &loopControl, true, false);
3035 		}
3036 	}
3037 
3038 	if (status != B_OK && status != kTrashCanceled && status != kUserCanceled) {
3039 		BAlert* alert = new BAlert("", B_TRANSLATE("Error emptying Trash"),
3040 			B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3041 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3042 			alert->Go();
3043 	}
3044 
3045 	return B_OK;
3046 }
3047 
3048 
3049 status_t
3050 _DeleteTask(BObjectList<entry_ref>* list, bool confirm)
3051 {
3052 	if (confirm) {
3053 		BAlert* alert = new BAlert("",
3054 			B_TRANSLATE_NOCOLLECT(kDeleteConfirmationStr),
3055 			B_TRANSLATE("Cancel"), B_TRANSLATE("Move to Trash"),
3056 			B_TRANSLATE("Delete"),
3057 			B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
3058 
3059 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3060 		alert->SetShortcut(0, B_ESCAPE);
3061 		alert->SetShortcut(1, 'm');
3062 		alert->SetShortcut(2, 'd');
3063 
3064 		switch (alert->Go()) {
3065 			case 0:
3066 				delete list;
3067 				return B_CANCELED;
3068 
3069 			case 1:
3070 			default:
3071 				FSMoveToTrash(list, NULL, false);
3072 				return B_OK;
3073 
3074 			case 2:
3075 				break;
3076 		}
3077 	}
3078 
3079 	TrackerCopyLoopControl loopControl(kDeleteState);
3080 
3081 	// calculate the sum total of all items on all volumes in trash
3082 	int32 totalItems = 0;
3083 	int64 totalSize = 0;
3084 
3085 	status_t status = CalcItemsAndSize(&loopControl, list, 0, &totalItems,
3086 		&totalSize);
3087 	if (status == B_OK) {
3088 		loopControl.Init(totalItems, totalItems);
3089 
3090 		int32 count = list->CountItems();
3091 		for (int32 index = 0; index < count; index++) {
3092 			entry_ref ref(*list->ItemAt(index));
3093 			BEntry entry(&ref);
3094 			loopControl.UpdateStatus(ref.name, ref, 1, true);
3095 			if (entry.IsDirectory())
3096 				status = FSDeleteFolder(&entry, &loopControl, true, true, true);
3097 			else
3098 				status = entry.Remove();
3099 		}
3100 
3101 		if (status != kTrashCanceled && status != kUserCanceled
3102 			&& status != B_OK) {
3103 			BAlert* alert = new BAlert("", B_TRANSLATE("Error deleting items"),
3104 				B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
3105 				B_WARNING_ALERT);
3106 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3107 			alert->Go();
3108 		}
3109 	}
3110 
3111 	delete list;
3112 
3113 	return B_OK;
3114 }
3115 
3116 status_t
3117 FSRecursiveCreateFolder(BPath path)
3118 {
3119 	BEntry entry(path.Path());
3120 	if (entry.InitCheck() != B_OK) {
3121 		BPath parentPath;
3122 		status_t err = path.GetParent(&parentPath);
3123 		if (err != B_OK)
3124 			return err;
3125 
3126 		err = FSRecursiveCreateFolder(parentPath);
3127 		if (err != B_OK)
3128 			return err;
3129 	}
3130 
3131 	entry.SetTo(path.Path());
3132 	if (entry.Exists())
3133 		return B_FILE_EXISTS;
3134 
3135 	BDirectory parent;
3136 	entry.GetParent(&parent);
3137 	parent.CreateDirectory(entry.Name(), NULL);
3138 
3139 	return B_OK;
3140 }
3141 
3142 status_t
3143 _RestoreTask(BObjectList<entry_ref>* list)
3144 {
3145 	TrackerCopyLoopControl loopControl(kRestoreFromTrashState);
3146 
3147 	// calculate the sum total of all items that will be restored
3148 	int32 totalItems = 0;
3149 	int64 totalSize = 0;
3150 
3151 	status_t err = CalcItemsAndSize(&loopControl, list, 0, &totalItems,
3152 		&totalSize);
3153 	if (err == B_OK) {
3154 		loopControl.Init(totalItems, totalItems);
3155 
3156 		int32 count = list->CountItems();
3157 		for (int32 index = 0; index < count; index++) {
3158 			entry_ref ref(*list->ItemAt(index));
3159 			BEntry entry(&ref);
3160 			BPath originalPath;
3161 
3162 			loopControl.UpdateStatus(ref.name, ref, 1, true);
3163 
3164 			if (FSGetOriginalPath(&entry, &originalPath) != B_OK)
3165 				continue;
3166 
3167 			BEntry originalEntry(originalPath.Path());
3168 			BPath parentPath;
3169 			err = originalPath.GetParent(&parentPath);
3170 			if (err != B_OK)
3171 				continue;
3172 			BEntry parentEntry(parentPath.Path());
3173 
3174 			if (parentEntry.InitCheck() != B_OK || !parentEntry.Exists()) {
3175 				if (FSRecursiveCreateFolder(parentPath) == B_OK) {
3176 					originalEntry.SetTo(originalPath.Path());
3177 					if (entry.InitCheck() != B_OK)
3178 						continue;
3179 				}
3180 			}
3181 
3182 			if (!originalEntry.Exists()) {
3183 				BDirectory dir(parentPath.Path());
3184 				if (dir.InitCheck() == B_OK) {
3185 					const char* leafName = originalEntry.Name();
3186 					if (entry.MoveTo(&dir, leafName) == B_OK) {
3187 						BNode node(&entry);
3188 						if (node.InitCheck() == B_OK)
3189 							node.RemoveAttr(kAttrOriginalPath);
3190 					}
3191 				}
3192 			}
3193 
3194 			err = loopControl.CheckUserCanceled();
3195 			if (err != B_OK)
3196 				break;
3197 		}
3198 	}
3199 
3200 	delete list;
3201 
3202 	return err;
3203 }
3204 
3205 void
3206 FSCreateTrashDirs()
3207 {
3208 	BVolume volume;
3209 	BVolumeRoster roster;
3210 
3211 	roster.Rewind();
3212 	while (roster.GetNextVolume(&volume) == B_OK) {
3213 		if (volume.IsReadOnly() || !volume.IsPersistent())
3214 			continue;
3215 
3216 		BDirectory trashDir;
3217 		FSGetTrashDir(&trashDir, volume.Device());
3218 	}
3219 }
3220 
3221 
3222 status_t
3223 FSCreateNewFolder(entry_ref* ref)
3224 {
3225 	node_ref node;
3226 	node.device = ref->device;
3227 	node.node = ref->directory;
3228 
3229 	BDirectory dir(&node);
3230 	status_t result = dir.InitCheck();
3231 	if (result != B_OK)
3232 		return result;
3233 
3234 	// ToDo: is that really necessary here?
3235 	BString name(ref->name);
3236 	FSMakeOriginalName(name, &dir, " -");
3237 	ref->set_name(name.String()); // update ref in case the folder got renamed
3238 
3239 	BDirectory newDir;
3240 	result = dir.CreateDirectory(name.String(), &newDir);
3241 	if (result != B_OK)
3242 		return result;
3243 
3244 	BNodeInfo nodeInfo(&newDir);
3245 	nodeInfo.SetType(B_DIR_MIMETYPE);
3246 
3247 	return result;
3248 }
3249 
3250 
3251 status_t
3252 FSCreateNewFolderIn(const node_ref* dirNode, entry_ref* newRef,
3253 	node_ref* newNode)
3254 {
3255 	BDirectory dir(dirNode);
3256 	status_t result = dir.InitCheck();
3257 	if (result == B_OK) {
3258 		char name[B_FILE_NAME_LENGTH];
3259 		strlcpy(name, B_TRANSLATE("New folder"), sizeof(name));
3260 
3261 		int fnum = 1;
3262 		while (dir.Contains(name)) {
3263 			// if base name already exists then add a number
3264 			// TODO: move this logic to FSMakeOriginalName
3265 			if (++fnum > 9)
3266 				snprintf(name, sizeof(name), B_TRANSLATE("New folder%d"), fnum);
3267 			else
3268 				snprintf(name, sizeof(name), B_TRANSLATE("New folder %d"), fnum);
3269 		}
3270 
3271 		BDirectory newDir;
3272 		result = dir.CreateDirectory(name, &newDir);
3273 		if (result == B_OK) {
3274 			BEntry entry;
3275 			newDir.GetEntry(&entry);
3276 			entry.GetRef(newRef);
3277 			entry.GetNodeRef(newNode);
3278 
3279 			BNodeInfo nodeInfo(&newDir);
3280 			nodeInfo.SetType(B_DIR_MIMETYPE);
3281 
3282 			// add undo item
3283 			NewFolderUndo undo(*newRef);
3284 			return B_OK;
3285 		}
3286 	}
3287 
3288 	BAlert* alert = new BAlert("",
3289 		B_TRANSLATE("Sorry, could not create a new folder."),
3290 		B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3291 	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3292 	alert->Go();
3293 
3294 	return result;
3295 }
3296 
3297 
3298 ReadAttrResult
3299 ReadAttr(const BNode* node, const char* hostAttrName,
3300 	const char* foreignAttrName, type_code type, off_t offset, void* buffer,
3301 	size_t length, void (*swapFunc)(void*), bool isForeign)
3302 {
3303 	if (!isForeign && node->ReadAttr(hostAttrName, type, offset, buffer,
3304 			length) == (ssize_t)length) {
3305 		return kReadAttrNativeOK;
3306 	}
3307 
3308 	// PRINT(("trying %s\n", foreignAttrName));
3309 	// try the other endianness
3310 	if (node->ReadAttr(foreignAttrName, type, offset, buffer, length)
3311 			!= (ssize_t)length) {
3312 		return kReadAttrFailed;
3313 	}
3314 
3315 	// PRINT(("got %s\n", foreignAttrName));
3316 	if (!swapFunc)
3317 		return kReadAttrForeignOK;
3318 
3319 	(swapFunc)(buffer);
3320 		// run the endian swapper
3321 
3322 	return kReadAttrForeignOK;
3323 }
3324 
3325 
3326 ReadAttrResult
3327 GetAttrInfo(const BNode* node, const char* hostAttrName,
3328 	const char* foreignAttrName, type_code* type, size_t* size)
3329 {
3330 	attr_info info;
3331 
3332 	if (node->GetAttrInfo(hostAttrName, &info) == B_OK) {
3333 		if (type)
3334 			*type = info.type;
3335 		if (size)
3336 			*size = (size_t)info.size;
3337 
3338 		return kReadAttrNativeOK;
3339 	}
3340 
3341 	if (node->GetAttrInfo(foreignAttrName, &info) == B_OK) {
3342 		if (type)
3343 			*type = info.type;
3344 		if (size)
3345 			*size = (size_t)info.size;
3346 
3347 		return kReadAttrForeignOK;
3348 	}
3349 	return kReadAttrFailed;
3350 }
3351 
3352 
3353 status_t
3354 FSGetParentVirtualDirectoryAware(const BEntry& entry, entry_ref& _ref)
3355 {
3356 	node_ref nodeRef;
3357 	if (entry.GetNodeRef(&nodeRef) == B_OK) {
3358 		if (VirtualDirectoryManager* manager
3359 				= VirtualDirectoryManager::Instance()) {
3360 			AutoLocker<VirtualDirectoryManager> managerLocker(manager);
3361 			if (manager->GetParentDirectoryDefinitionFile(nodeRef, _ref,
3362 					nodeRef)) {
3363 				return B_OK;
3364 			}
3365 		}
3366 	}
3367 
3368 	status_t error;
3369 	BDirectory parent;
3370 	BEntry parentEntry;
3371 	if ((error = entry.GetParent(&parent)) != B_OK
3372 		|| (error = parent.GetEntry(&parentEntry)) != B_OK
3373 		|| (error = parentEntry.GetRef(&_ref)) != B_OK) {
3374 		return error;
3375 	}
3376 
3377 	return B_OK;
3378 }
3379 
3380 
3381 status_t
3382 FSGetParentVirtualDirectoryAware(const BEntry& entry, BEntry& _entry)
3383 {
3384 	node_ref nodeRef;
3385 	if (entry.GetNodeRef(&nodeRef) == B_OK) {
3386 		if (VirtualDirectoryManager* manager
3387 				= VirtualDirectoryManager::Instance()) {
3388 			AutoLocker<VirtualDirectoryManager> managerLocker(manager);
3389 			entry_ref parentRef;
3390 			if (manager->GetParentDirectoryDefinitionFile(nodeRef, parentRef,
3391 					nodeRef)) {
3392 				return _entry.SetTo(&parentRef);
3393 			}
3394 		}
3395 	}
3396 
3397 	return entry.GetParent(&_entry);
3398 }
3399 
3400 
3401 status_t
3402 FSGetParentVirtualDirectoryAware(const BEntry& entry, BNode& _node)
3403 {
3404 	entry_ref ref;
3405 	status_t error = FSGetParentVirtualDirectoryAware(entry, ref);
3406 	if (error == B_OK)
3407 		error = _node.SetTo(&ref);
3408 
3409 	return error;
3410 }
3411 
3412 
3413 // launching code
3414 
3415 static status_t
3416 TrackerOpenWith(const BMessage* refs)
3417 {
3418 	BMessage clone(*refs);
3419 
3420 	ASSERT(dynamic_cast<TTracker*>(be_app) != NULL);
3421 	ASSERT(clone.what != 0);
3422 
3423 	clone.AddInt32("launchUsingSelector", 0);
3424 	// runs the Open With window
3425 	be_app->PostMessage(&clone);
3426 
3427 	return B_OK;
3428 }
3429 
3430 
3431 static void
3432 AsynchLaunchBinder(void (*func)(const entry_ref*, const BMessage*, bool on),
3433 	const entry_ref* appRef, const BMessage* refs, bool openWithOK)
3434 {
3435 	BMessage* task = new BMessage;
3436 	task->AddPointer("function", (void*)func);
3437 	task->AddMessage("refs", refs);
3438 	task->AddBool("openWithOK", openWithOK);
3439 	if (appRef != NULL)
3440 		task->AddRef("appRef", appRef);
3441 
3442 	extern BLooper* gLaunchLooper;
3443 	gLaunchLooper->PostMessage(task);
3444 }
3445 
3446 
3447 static bool
3448 SniffIfGeneric(const entry_ref* ref)
3449 {
3450 	BNode node(ref);
3451 	char type[B_MIME_TYPE_LENGTH];
3452 	BNodeInfo info(&node);
3453 	if (info.GetType(type) == B_OK
3454 		&& strcasecmp(type, B_FILE_MIME_TYPE) != 0) {
3455 		// already has a type and it's not octet stream
3456 		return false;
3457 	}
3458 
3459 	BPath path(ref);
3460 	if (path.Path()) {
3461 		// force a mimeset
3462 		node.RemoveAttr(kAttrMIMEType);
3463 		update_mime_info(path.Path(), 0, 1, 1);
3464 	}
3465 
3466 	return true;
3467 }
3468 
3469 
3470 static void
3471 SniffIfGeneric(const BMessage* refs)
3472 {
3473 	entry_ref ref;
3474 	for (int32 index = 0; ; index++) {
3475 		if (refs->FindRef("refs", index, &ref) != B_OK)
3476 			break;
3477 		SniffIfGeneric(&ref);
3478 	}
3479 }
3480 
3481 
3482 static void
3483 _TrackerLaunchAppWithDocuments(const entry_ref* appRef, const BMessage* refs,
3484 	bool openWithOK)
3485 {
3486 	team_id team;
3487 
3488 	status_t error = B_ERROR;
3489 	BString alertString;
3490 
3491 	for (int32 mimesetIt = 0; ; mimesetIt++) {
3492 		error = be_roster->Launch(appRef, refs, &team);
3493 		if (error == B_ALREADY_RUNNING)
3494 			// app already running, not really an error
3495 			error = B_OK;
3496 
3497 		if (error == B_OK)
3498 			break;
3499 
3500 		if (mimesetIt > 0)
3501 			break;
3502 
3503 		// failed to open, try mimesetting the refs and launching again
3504 		SniffIfGeneric(refs);
3505 	}
3506 
3507 	if (error == B_OK) {
3508 		// close possible parent window, if specified
3509 		const node_ref* nodeToClose = 0;
3510 		ssize_t numBytes;
3511 		if (refs != NULL && refs->FindData("nodeRefsToClose", B_RAW_TYPE,
3512 				(const void**)&nodeToClose, &numBytes) == B_OK
3513 			&& nodeToClose != NULL) {
3514 			TTracker* tracker = dynamic_cast<TTracker*>(be_app);
3515 			if (tracker != NULL)
3516 				tracker->CloseParent(*nodeToClose);
3517 		}
3518 	} else {
3519 		alertString.SetTo(B_TRANSLATE("Could not open \"%name\" (%error). "));
3520 		alertString.ReplaceFirst("%name", appRef->name);
3521 		alertString.ReplaceFirst("%error", strerror(error));
3522 		if (refs != NULL && openWithOK && error != B_SHUTTING_DOWN) {
3523 			alertString << B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3524 			BAlert* alert = new BAlert("", alertString.String(),
3525 				B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0,
3526 				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3527 			alert->SetShortcut(0, B_ESCAPE);
3528 			if (alert->Go() == 1)
3529 				error = TrackerOpenWith(refs);
3530 		} else {
3531 			BAlert* alert = new BAlert("", alertString.String(),
3532 				B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
3533 				B_WARNING_ALERT);
3534 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3535 			alert->Go();
3536 		}
3537 	}
3538 }
3539 
3540 
3541 extern "C" char** environ;
3542 
3543 
3544 static status_t
3545 LoaderErrorDetails(const entry_ref* app, BString &details)
3546 {
3547 	BPath path;
3548 	BEntry appEntry(app, true);
3549 
3550 	status_t result = appEntry.GetPath(&path);
3551 	if (result != B_OK)
3552 		return result;
3553 
3554 	char* argv[2] = { const_cast<char*>(path.Path()), 0};
3555 
3556 	port_id errorPort = create_port(1, "Tracker loader error");
3557 
3558 	// count environment variables
3559 	int32 envCount = 0;
3560 	while (environ[envCount] != NULL)
3561 		envCount++;
3562 
3563 	char** flatArgs = NULL;
3564 	size_t flatArgsSize;
3565 	result = __flatten_process_args((const char**)argv, 1,
3566 		environ, &envCount, argv[0], &flatArgs, &flatArgsSize);
3567 	if (result != B_OK)
3568 		return result;
3569 
3570 	result = _kern_load_image(flatArgs, flatArgsSize, 1, envCount,
3571 		B_NORMAL_PRIORITY, B_WAIT_TILL_LOADED, errorPort, 0);
3572 	if (result == B_OK) {
3573 		// we weren't supposed to be able to start the application...
3574 		return B_ERROR;
3575 	}
3576 
3577 	// read error message from port and construct details string
3578 
3579 	ssize_t bufferSize;
3580 
3581 	do {
3582 		bufferSize = port_buffer_size_etc(errorPort, B_RELATIVE_TIMEOUT, 0);
3583 	} while (bufferSize == B_INTERRUPTED);
3584 
3585 	if (bufferSize <= B_OK) {
3586 		delete_port(errorPort);
3587 		return bufferSize;
3588 	}
3589 
3590 	uint8* buffer = (uint8*)malloc(bufferSize);
3591 	if (buffer == NULL) {
3592 		delete_port(errorPort);
3593 		return B_NO_MEMORY;
3594 	}
3595 
3596 	bufferSize = read_port_etc(errorPort, NULL, buffer, bufferSize,
3597 		B_RELATIVE_TIMEOUT, 0);
3598 	delete_port(errorPort);
3599 
3600 	if (bufferSize < B_OK) {
3601 		free(buffer);
3602 		return bufferSize;
3603 	}
3604 
3605 	BMessage message;
3606 	result = message.Unflatten((const char*)buffer);
3607 	free(buffer);
3608 
3609 	if (result != B_OK)
3610 		return result;
3611 
3612 	int32 errorCode = B_ERROR;
3613 	result = message.FindInt32("error", &errorCode);
3614 	if (result != B_OK)
3615 		return result;
3616 
3617 	const char* detailName = NULL;
3618 	switch (errorCode) {
3619 		case B_MISSING_LIBRARY:
3620 			detailName = "missing library";
3621 			break;
3622 
3623 		case B_MISSING_SYMBOL:
3624 			detailName = "missing symbol";
3625 			break;
3626 	}
3627 
3628 	if (detailName == NULL)
3629 		return B_ERROR;
3630 
3631 	const char* detail;
3632 	for (int32 i = 0; message.FindString(detailName, i, &detail) == B_OK;
3633 			i++) {
3634 		if (i > 0)
3635 			details += ", ";
3636 		details += detail;
3637 	}
3638 
3639 	return B_OK;
3640 }
3641 
3642 
3643 static void
3644 _TrackerLaunchDocuments(const entry_ref*, const BMessage* refs,
3645 	bool openWithOK)
3646 {
3647 	if (refs == NULL)
3648 		return;
3649 
3650 	BMessage copyOfRefs(*refs);
3651 
3652 	entry_ref documentRef;
3653 	if (copyOfRefs.FindRef("refs", &documentRef) != B_OK) {
3654 		// nothing to launch, we are done
3655 		return;
3656 	}
3657 
3658 	status_t error = B_ERROR;
3659 	entry_ref app;
3660 	BMessage* refsToPass = NULL;
3661 	BString alertString;
3662 	const char* alternative = 0;
3663 
3664 	for (int32 mimesetIt = 0; ; mimesetIt++) {
3665 		alertString = "";
3666 		error = be_roster->FindApp(&documentRef, &app);
3667 
3668 		if (error != B_OK && mimesetIt == 0) {
3669 			SniffIfGeneric(&copyOfRefs);
3670 			continue;
3671 		}
3672 
3673 		if (error != B_OK) {
3674 			alertString.SetTo(B_TRANSLATE("Could not find an application to "
3675 				"open \"%name\" (%error). "));
3676 			alertString.ReplaceFirst("%name", documentRef.name);
3677 			alertString.ReplaceFirst("%error", strerror(error));
3678 			if (openWithOK)
3679 				alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr);
3680 
3681 			break;
3682 		} else {
3683 			BEntry appEntry(&app, true);
3684 			for (int32 index = 0;;) {
3685 				// remove the app itself from the refs received so we don't
3686 				// try to open ourselves
3687 				entry_ref ref;
3688 				if (copyOfRefs.FindRef("refs", index, &ref) != B_OK)
3689 					break;
3690 
3691 				// deal with symlinks properly
3692 				BEntry documentEntry(&ref, true);
3693 				if (appEntry == documentEntry) {
3694 					PRINT(("stripping %s, app %s \n", ref.name, app.name));
3695 					copyOfRefs.RemoveData("refs", index);
3696 				} else {
3697 					PRINT(("leaving %s, app %s  \n", ref.name, app.name));
3698 					index++;
3699 				}
3700 			}
3701 
3702 			refsToPass = CountRefs(&copyOfRefs) > 0 ? &copyOfRefs: 0;
3703 			team_id team;
3704 			error = be_roster->Launch(&app, refsToPass, &team);
3705 			if (error == B_ALREADY_RUNNING)
3706 				// app already running, not really an error
3707 				error = B_OK;
3708 			if (error == B_OK || mimesetIt != 0)
3709 				break;
3710 			if (error == B_LAUNCH_FAILED_EXECUTABLE) {
3711 				BVolume volume(documentRef.device);
3712 				if (volume.IsReadOnly()) {
3713 					BMimeType type;
3714 					error = BMimeType::GuessMimeType(&documentRef, &type);
3715 					if (error != B_OK)
3716 						break;
3717 					error = be_roster->FindApp(type.Type(), &app);
3718 					if (error != B_OK)
3719 						break;
3720 					error = be_roster->Launch(&app, refs, &team);
3721 					if (error == B_ALREADY_RUNNING)
3722 						// app already running, not really an error
3723 						error = B_OK;
3724 					if (error == B_OK || mimesetIt != 0)
3725 						break;
3726 				}
3727 			}
3728 
3729 			SniffIfGeneric(&copyOfRefs);
3730 		}
3731 	}
3732 
3733 	if (error != B_OK && alertString.Length() == 0) {
3734 		BString loaderErrorString;
3735 		bool openedDocuments = true;
3736 
3737 		if (!refsToPass) {
3738 			// we just double clicked the app itself, do not offer to
3739 			// find a handling app
3740 			openWithOK = false;
3741 			openedDocuments = false;
3742 		}
3743 		if (error == B_UNKNOWN_EXECUTABLE && !refsToPass) {
3744 			// We know it's an executable, but something unsupported
3745 			alertString.SetTo(B_TRANSLATE("\"%name\" is an unsupported "
3746 				"executable."));
3747 			alertString.ReplaceFirst("%name", app.name);
3748 		} else if (error == B_LEGACY_EXECUTABLE && !refsToPass) {
3749 			// For the moment, this marks an old R3 binary, we may want to
3750 			// extend it to gcc2 binaries someday post R1
3751 			alertString.SetTo(B_TRANSLATE("\"%name\" is a legacy executable. "
3752 				"Please obtain an updated version or recompile "
3753 				"the application."));
3754 			alertString.ReplaceFirst("%name", app.name);
3755 		} else if (error == B_LAUNCH_FAILED_EXECUTABLE && !refsToPass) {
3756 			alertString.SetTo(B_TRANSLATE("Could not open \"%name\". "
3757 				"The file is mistakenly marked as executable. "));
3758 			alertString.ReplaceFirst("%name", app.name);
3759 
3760 			if (!openWithOK) {
3761 				// offer the possibility to change the permissions
3762 
3763 				alertString << B_TRANSLATE("\nShould this be fixed?");
3764 				BAlert* alert = new BAlert("", alertString.String(),
3765 					B_TRANSLATE("Cancel"), B_TRANSLATE("Proceed"), 0,
3766 					B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3767 				alert->SetShortcut(0, B_ESCAPE);
3768 				if (alert->Go() == 1) {
3769 					BEntry entry(&documentRef);
3770 					mode_t permissions;
3771 
3772 					error = entry.GetPermissions(&permissions);
3773 					if (error == B_OK) {
3774 						error = entry.SetPermissions(permissions
3775 							& ~(S_IXUSR | S_IXGRP | S_IXOTH));
3776 					}
3777 					if (error == B_OK) {
3778 						// we updated the permissions, so let's try again
3779 						_TrackerLaunchDocuments(NULL, refs, false);
3780 						return;
3781 					} else {
3782 						alertString.SetTo(B_TRANSLATE("Could not update "
3783 							"permissions of file \"%name\". %error"));
3784 						alertString.ReplaceFirst("%name", app.name);
3785 						alertString.ReplaceFirst("%error", strerror(error));
3786 					}
3787 				} else
3788 					return;
3789 			}
3790 
3791 			alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr);
3792 		} else if (error == B_LAUNCH_FAILED_APP_IN_TRASH) {
3793 			alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3794 				"because application \"%app\" is in the Trash. "));
3795 			alertString.ReplaceFirst("%document", documentRef.name);
3796 			alertString.ReplaceFirst("%app", app.name);
3797 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3798 		} else if (error == B_LAUNCH_FAILED_APP_NOT_FOUND) {
3799 			alertString.SetTo(
3800 				B_TRANSLATE("Could not open \"%name\" (%error). "));
3801 			alertString.ReplaceFirst("%name", documentRef.name);
3802 			alertString.ReplaceFirst("%error", strerror(error));
3803 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3804 		} else if (error == B_MISSING_SYMBOL
3805 			&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3806 			if (openedDocuments) {
3807 				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3808 					"with application \"%app\" (Missing symbol: %symbol). "
3809 					"\n"));
3810 				alertString.ReplaceFirst("%document", documentRef.name);
3811 				alertString.ReplaceFirst("%app", app.name);
3812 				alertString.ReplaceFirst("%symbol",
3813 					loaderErrorString.String());
3814 			} else {
3815 				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3816 					"(Missing symbol: %symbol). \n"));
3817 				alertString.ReplaceFirst("%document", documentRef.name);
3818 				alertString.ReplaceFirst("%symbol",
3819 					loaderErrorString.String());
3820 			}
3821 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3822 		} else if (error == B_MISSING_LIBRARY
3823 			&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3824 			if (openedDocuments) {
3825 				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3826 					"with application \"%app\" (Missing libraries: %library). "
3827 					"\n"));
3828 				alertString.ReplaceFirst("%document", documentRef.name);
3829 				alertString.ReplaceFirst("%app", app.name);
3830 				alertString.ReplaceFirst("%library",
3831 					loaderErrorString.String());
3832 			} else {
3833 				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3834 					"(Missing libraries: %library). \n"));
3835 				alertString.ReplaceFirst("%document", documentRef.name);
3836 				alertString.ReplaceFirst("%library",
3837 					loaderErrorString.String());
3838 			}
3839 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3840 		} else {
3841 			alertString.SetTo(B_TRANSLATE("Could not open \"%document\" with "
3842 				"application \"%app\" (%error). "));
3843 				alertString.ReplaceFirst("%document", documentRef.name);
3844 				alertString.ReplaceFirst("%app", app.name);
3845 				alertString.ReplaceFirst("%error", strerror(error));
3846 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3847 		}
3848 	}
3849 
3850 	if (error != B_OK) {
3851 		if (openWithOK) {
3852 			ASSERT(alternative);
3853 			alertString << alternative;
3854 			BAlert* alert = new BAlert("", alertString.String(),
3855 				B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0,
3856 				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3857 			alert->SetShortcut(0, B_ESCAPE);
3858 			if (alert->Go() == 1)
3859 				error = TrackerOpenWith(refs);
3860 		} else {
3861 			BAlert* alert = new BAlert("", alertString.String(),
3862 				B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
3863 				B_WARNING_ALERT);
3864 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3865 			alert->Go();
3866 		}
3867 	}
3868 }
3869 
3870 // the following three calls don't return any reasonable error codes,
3871 // should fix that, making them void
3872 
3873 status_t
3874 TrackerLaunch(const entry_ref* appRef, const BMessage* refs, bool async,
3875 	bool openWithOK)
3876 {
3877 	if (!async)
3878 		_TrackerLaunchAppWithDocuments(appRef, refs, openWithOK);
3879 	else {
3880 		AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, refs,
3881 			openWithOK);
3882 	}
3883 
3884 	return B_OK;
3885 }
3886 
3887 status_t
3888 TrackerLaunch(const entry_ref* appRef, bool async)
3889 {
3890 	if (!async)
3891 		_TrackerLaunchAppWithDocuments(appRef, NULL, false);
3892 	else
3893 		AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, 0, false);
3894 
3895 	return B_OK;
3896 }
3897 
3898 status_t
3899 TrackerLaunch(const BMessage* refs, bool async, bool openWithOK)
3900 {
3901 	if (!async)
3902 		_TrackerLaunchDocuments(NULL, refs, openWithOK);
3903 	else
3904 		AsynchLaunchBinder(&_TrackerLaunchDocuments, NULL, refs, openWithOK);
3905 
3906 	return B_OK;
3907 }
3908 
3909 
3910 // external launch calls; need to be robust, work if Tracker is not running
3911 
3912 
3913 #if !B_BEOS_VERSION_DANO
3914 _IMPEXP_TRACKER
3915 #endif
3916 status_t
3917 FSLaunchItem(const entry_ref* application, const BMessage* refsReceived,
3918 	bool async, bool openWithOK)
3919 {
3920 	return TrackerLaunch(application, refsReceived, async, openWithOK);
3921 }
3922 
3923 
3924 #if !B_BEOS_VERSION_DANO
3925 _IMPEXP_TRACKER
3926 #endif
3927 status_t
3928 FSOpenWith(BMessage* listOfRefs)
3929 {
3930 	status_t result = B_ERROR;
3931 	listOfRefs->what = B_REFS_RECEIVED;
3932 
3933 	if (dynamic_cast<TTracker*>(be_app) != NULL)
3934 		result = TrackerOpenWith(listOfRefs);
3935 	else
3936 		ASSERT(!"not yet implemented");
3937 
3938 	return result;
3939 }
3940 
3941 
3942 // legacy calls, need for compatibility
3943 
3944 
3945 void
3946 FSOpenWithDocuments(const entry_ref* executable, BMessage* documents)
3947 {
3948 	TrackerLaunch(executable, documents, true);
3949 	delete documents;
3950 }
3951 
3952 
3953 status_t
3954 FSLaunchUsing(const entry_ref* ref, BMessage* listOfRefs)
3955 {
3956 	BMessage temp(B_REFS_RECEIVED);
3957 	if (listOfRefs == NULL) {
3958 		ASSERT(ref != NULL);
3959 		temp.AddRef("refs", ref);
3960 		listOfRefs = &temp;
3961 	}
3962 	FSOpenWith(listOfRefs);
3963 
3964 	return B_OK;
3965 }
3966 
3967 
3968 status_t
3969 FSLaunchItem(const entry_ref* appRef, BMessage* refs, int32, bool async)
3970 {
3971 	if (refs != NULL)
3972 		refs->what = B_REFS_RECEIVED;
3973 
3974 	status_t result = TrackerLaunch(appRef, refs, async, true);
3975 	delete refs;
3976 
3977 	return result;
3978 }
3979 
3980 
3981 void
3982 FSLaunchItem(const entry_ref* appRef, BMessage* refs, int32 workspace)
3983 {
3984 	FSLaunchItem(appRef, refs, workspace, true);
3985 }
3986 
3987 
3988 // Get the original path of an entry in the trash
3989 status_t
3990 FSGetOriginalPath(BEntry* entry, BPath* result)
3991 {
3992 	status_t err;
3993 	entry_ref ref;
3994 	err = entry->GetRef(&ref);
3995 	if (err != B_OK)
3996 		return err;
3997 
3998 	// Only call the routine for entries in the trash
3999 	if (!FSInTrashDir(&ref))
4000 		return B_ERROR;
4001 
4002 	BNode node(entry);
4003 	BString originalPath;
4004 	if (node.ReadAttrString(kAttrOriginalPath, &originalPath) == B_OK) {
4005 		// We're in luck, the entry has the original path in an attribute
4006 		err = result->SetTo(originalPath.String());
4007 		return err;
4008 	}
4009 
4010 	// Iterate the parent directories to find one with
4011 	// the original path attribute
4012 	BEntry parent(*entry);
4013 	err = parent.InitCheck();
4014 	if (err != B_OK)
4015 		return err;
4016 
4017 	// walk up the directory structure until we find a node
4018 	// with original path attribute
4019 	do {
4020 		// move to the parent of this node
4021 		err = parent.GetParent(&parent);
4022 		if (err != B_OK)
4023 			return err;
4024 
4025 		// return if we are at the root of the trash
4026 		if (FSIsTrashDir(&parent))
4027 			return B_ENTRY_NOT_FOUND;
4028 
4029 		// get the parent as a node
4030 		err = node.SetTo(&parent);
4031 		if (err != B_OK)
4032 			return err;
4033 	} while (node.ReadAttrString(kAttrOriginalPath, &originalPath) != B_OK);
4034 
4035 	// Found the attribute, figure out there this file
4036 	// used to live, based on the successfully-read attribute
4037 	err = result->SetTo(originalPath.String());
4038 	if (err != B_OK)
4039 		return err;
4040 
4041 	BPath path, pathParent;
4042 	err = parent.GetPath(&pathParent);
4043 	if (err != B_OK)
4044 		return err;
4045 
4046 	err = entry->GetPath(&path);
4047 	if (err != B_OK)
4048 		return err;
4049 
4050 	result->Append(path.Path() + strlen(pathParent.Path()) + 1);
4051 		// compute the new path by appending the offset of
4052 		// the item we are locating, to the original path
4053 		// of the parent
4054 
4055 	return B_OK;
4056 }
4057 
4058 
4059 directory_which
4060 WellKnowEntryList::Match(const node_ref* node)
4061 {
4062 	const WellKnownEntry* result = MatchEntry(node);
4063 	if (result != NULL)
4064 		return result->which;
4065 
4066 	return (directory_which)-1;
4067 }
4068 
4069 
4070 const WellKnowEntryList::WellKnownEntry*
4071 WellKnowEntryList::MatchEntry(const node_ref* node)
4072 {
4073 	if (self == NULL)
4074 		self = new WellKnowEntryList();
4075 
4076 	return self->MatchEntryCommon(node);
4077 }
4078 
4079 
4080 const WellKnowEntryList::WellKnownEntry*
4081 WellKnowEntryList::MatchEntryCommon(const node_ref* node)
4082 {
4083 	uint32 count = entries.size();
4084 	for (uint32 index = 0; index < count; index++) {
4085 		if (*node == entries[index].node)
4086 			return &entries[index];
4087 	}
4088 
4089 	return NULL;
4090 }
4091 
4092 
4093 void
4094 WellKnowEntryList::Quit()
4095 {
4096 	delete self;
4097 	self = NULL;
4098 }
4099 
4100 
4101 void
4102 WellKnowEntryList::AddOne(directory_which which, const char* name)
4103 {
4104 	BPath path;
4105 	if (find_directory(which, &path, true) != B_OK)
4106 		return;
4107 
4108 	BEntry entry(path.Path(), true);
4109 	node_ref node;
4110 	if (entry.GetNodeRef(&node) != B_OK)
4111 		return;
4112 
4113 	entries.push_back(WellKnownEntry(&node, which, name));
4114 }
4115 
4116 
4117 void
4118 WellKnowEntryList::AddOne(directory_which which, directory_which base,
4119 	const char* extra, const char* name)
4120 {
4121 	BPath path;
4122 	if (find_directory(base, &path, true) != B_OK)
4123 		return;
4124 
4125 	path.Append(extra);
4126 	BEntry entry(path.Path(), true);
4127 	node_ref node;
4128 	if (entry.GetNodeRef(&node) != B_OK)
4129 		return;
4130 
4131 	entries.push_back(WellKnownEntry(&node, which, name));
4132 }
4133 
4134 
4135 void
4136 WellKnowEntryList::AddOne(directory_which which, const char* path,
4137 	const char* name)
4138 {
4139 	BEntry entry(path, true);
4140 	node_ref node;
4141 	if (entry.GetNodeRef(&node) != B_OK)
4142 		return;
4143 
4144 	entries.push_back(WellKnownEntry(&node, which, name));
4145 }
4146 
4147 
4148 WellKnowEntryList::WellKnowEntryList()
4149 {
4150 	AddOne(B_SYSTEM_DIRECTORY, "system");
4151 	AddOne((directory_which)B_BOOT_DISK, "/boot", "boot");
4152 	AddOne(B_USER_DIRECTORY, "home");
4153 
4154 	AddOne(B_BEOS_FONTS_DIRECTORY, "fonts");
4155 	AddOne(B_USER_FONTS_DIRECTORY, "fonts");
4156 
4157 	AddOne(B_BEOS_APPS_DIRECTORY, "apps");
4158 	AddOne(B_APPS_DIRECTORY, "apps");
4159 	AddOne((directory_which)B_USER_DESKBAR_APPS_DIRECTORY,
4160 		B_USER_DESKBAR_DIRECTORY, "Applications", "apps");
4161 
4162 	AddOne(B_BEOS_PREFERENCES_DIRECTORY, "preferences");
4163 	AddOne(B_PREFERENCES_DIRECTORY, "preferences");
4164 	AddOne((directory_which)B_USER_DESKBAR_PREFERENCES_DIRECTORY,
4165 		B_USER_DESKBAR_DIRECTORY, "Preferences", "preferences");
4166 
4167 	AddOne((directory_which)B_USER_MAIL_DIRECTORY, B_USER_DIRECTORY, "mail",
4168 		"mail");
4169 
4170 	AddOne((directory_which)B_USER_QUERIES_DIRECTORY, B_USER_DIRECTORY,
4171 		"queries", "queries");
4172 
4173 	AddOne(B_SYSTEM_DEVELOP_DIRECTORY, "develop");
4174 	AddOne((directory_which)B_USER_DESKBAR_DEVELOP_DIRECTORY,
4175 		B_USER_DESKBAR_DIRECTORY, "Development", "develop");
4176 
4177 	AddOne(B_USER_CONFIG_DIRECTORY, "config");
4178 
4179 	AddOne((directory_which)B_USER_PEOPLE_DIRECTORY, B_USER_DIRECTORY,
4180 		"people", "people");
4181 
4182 	AddOne((directory_which)B_USER_DOWNLOADS_DIRECTORY, B_USER_DIRECTORY,
4183 		"downloads", "downloads");
4184 }
4185 
4186 WellKnowEntryList* WellKnowEntryList::self = NULL;
4187 
4188 } // namespace BPrivate
4189