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