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