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