xref: /haiku/src/kits/storage/Path.cpp (revision 2510baa4685f8f570c607ceedfd73473d69342c4)
1 /*
2  * Copyright 2002-2012, Haiku Inc.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Tyler Dauwalder
7  *		Ingo Weinhold, bonefish@users.sf.net
8  */
9 
10 
11 /*!
12 	\file Path.cpp
13 	BPath implementation.
14 */
15 
16 
17 #include <Path.h>
18 
19 #include <new>
20 
21 #include <Directory.h>
22 #include <Entry.h>
23 #include <StorageDefs.h>
24 #include <String.h>
25 
26 #include <syscalls.h>
27 
28 #include "storage_support.h"
29 
30 using namespace std;
31 
32 
33 //! Creates an uninitialized BPath object.
34 BPath::BPath()
35 	:
36 	fName(NULL),
37 	fCStatus(B_NO_INIT)
38 {
39 }
40 
41 
42 /*! Creates a copy of the given BPath object.
43 	\param path the object to be copied
44 */
45 BPath::BPath(const BPath& path)
46 	:
47 	fName(NULL),
48 	fCStatus(B_NO_INIT)
49 {
50 	*this = path;
51 }
52 
53 
54 /*!	\brief Creates a BPath object and initializes it to the filesystem entry
55 	specified by the given entry_ref struct.
56 	\param ref the entry_ref
57 */
58 BPath::BPath(const entry_ref* ref)
59 	:
60 	fName(NULL),
61 	fCStatus(B_NO_INIT)
62 {
63 	SetTo(ref);
64 }
65 
66 
67 /*!	\brief Creates a BPath object and initializes it to the filesystem entry
68 	specified by the given BEntry object.
69 	\param entry the BEntry object
70 */
71 BPath::BPath(const BEntry* entry)
72 	:
73 	fName(NULL),
74 	fCStatus(B_NO_INIT)
75 {
76 	SetTo(entry);
77 }
78 
79 
80 /*! \brief Creates a BPath object and initializes it to the specified path or
81 		   path and filename combination.
82 
83 	\param dir The base component of the pathname. May be absolute or relative.
84 		   If relative, it is reckoned off the current working directory.
85 	\param leaf The (optional) leaf component of the pathname. Must be
86 		   relative. The value of leaf is concatenated to the end of \a dir
87 		   (a "/" will be added as a separator, if necessary).
88 	\param normalize boolean flag used to force normalization; normalization
89 		   may occur even if false (see \ref _MustNormalize).
90 */
91 BPath::BPath(const char* dir, const char* leaf, bool normalize)
92 	:
93 	fName(NULL),
94 	fCStatus(B_NO_INIT)
95 {
96 	SetTo(dir, leaf, normalize);
97 }
98 
99 
100 /*! \brief Creates a BPath object and initializes it to the specified directory
101 	 and filename combination.
102 	\param dir Refers to the directory that provides the base component of the
103 		   pathname.
104 	\param leaf The (optional) leaf component of the pathname. Must be
105 		   relative. The value of leaf is concatenated to the end of \a dir
106 		   (a "/" will be added as a separator, if necessary).
107 	\param normalize boolean flag used to force normalization; normalization
108 		   may occur even if false (see \ref _MustNormalize).
109 */
110 BPath::BPath(const BDirectory* dir, const char* leaf, bool normalize)
111 	:
112 	fName(NULL),
113 	fCStatus(B_NO_INIT)
114 {
115 	SetTo(dir, leaf, normalize);
116 }
117 
118 
119 //! Destroys the BPath object and frees any of its associated resources.
120 BPath::~BPath()
121 {
122 	Unset();
123 }
124 
125 
126 /*! \brief Returns the status of the most recent construction or SetTo() call.
127 	\return \c B_OK, if the BPath object is properly initialized, an error
128 			code otherwise.
129 */
130 status_t
131 BPath::InitCheck() const
132 {
133 	return fCStatus;
134 }
135 
136 
137 /*! \brief Reinitializes the object to the filesystem entry specified by the
138 	given entry_ref struct.
139 	\param ref the entry_ref
140 	\return
141 	- \c B_OK: The initialization was successful.
142 	- \c B_BAD_VALUE: \c NULL \a ref.
143 	- \c B_NAME_TOO_LONG: The pathname is longer than \c B_PATH_NAME_LENGTH.
144 	- other error codes.
145 */
146 status_t
147 BPath::SetTo(const entry_ref* ref)
148 {
149 	Unset();
150 	if (!ref)
151 		return fCStatus = B_BAD_VALUE;
152 
153 	char path[B_PATH_NAME_LENGTH];
154 	fCStatus = _kern_entry_ref_to_path(ref->device, ref->directory,
155 		ref->name, path, sizeof(path));
156 	if (fCStatus != B_OK)
157 		return fCStatus;
158 
159 	fCStatus = _SetPath(path);
160 		// the path is already normalized
161 	return fCStatus;
162 }
163 
164 
165 /*! \brief Reinitializes the object to the specified filesystem entry.
166 	\param entry the BEntry
167 	\return
168 	- \c B_OK: The initialization was successful.
169 	- \c B_BAD_VALUE: \c NULL \a entry.
170 	- \c B_NAME_TOO_LONG: The pathname is longer than \c B_PATH_NAME_LENGTH.
171 	- other error codes.
172 */
173 status_t
174 BPath::SetTo(const BEntry* entry)
175 {
176 	Unset();
177 	if (entry == NULL)
178 		return B_BAD_VALUE;
179 
180 	entry_ref ref;
181 	fCStatus = entry->GetRef(&ref);
182 	if (fCStatus == B_OK)
183 		fCStatus = SetTo(&ref);
184 
185 	return fCStatus;
186 }
187 
188 
189 /*!	\brief Reinitializes the object to the specified path or path and file
190 	name combination.
191 	\param path the path name
192 	\param leaf the leaf name (may be \c NULL)
193 	\param normalize boolean flag used to force normalization; normalization
194 		   may occur even if false (see \ref _MustNormalize).
195 	\return
196 	- \c B_OK: The initialization was successful.
197 	- \c B_BAD_VALUE: \c NULL \a path or absolute \a leaf.
198 	- \c B_NAME_TOO_LONG: The pathname is longer than \c B_PATH_NAME_LENGTH.
199 	- other error codes.
200 	\note \code path.SetTo(path.Path(), "new leaf") \endcode is safe.
201 */
202 status_t
203 BPath::SetTo(const char* path, const char* leaf, bool normalize)
204 {
205 	status_t error = (path ? B_OK : B_BAD_VALUE);
206 	if (error == B_OK && leaf && BPrivate::Storage::is_absolute_path(leaf))
207 		error = B_BAD_VALUE;
208 	char newPath[B_PATH_NAME_LENGTH];
209 	if (error == B_OK) {
210 		// we always normalize relative paths
211 		normalize |= !BPrivate::Storage::is_absolute_path(path);
212 		// build a new path from path and leaf
213 		// copy path first
214 		uint32 pathLen = strlen(path);
215 		if (pathLen >= sizeof(newPath))
216 			error = B_NAME_TOO_LONG;
217 		if (error == B_OK)
218 			strcpy(newPath, path);
219 		// append leaf, if supplied
220 		if (error == B_OK && leaf) {
221 			bool needsSeparator = (pathLen > 0 && path[pathLen - 1] != '/');
222 			uint32 wholeLen = pathLen + (needsSeparator ? 1 : 0)
223 							  + strlen(leaf);
224 			if (wholeLen >= sizeof(newPath))
225 				error = B_NAME_TOO_LONG;
226 			if (error == B_OK) {
227 				if (needsSeparator) {
228 					newPath[pathLen] = '/';
229 					pathLen++;
230 				}
231 				strcpy(newPath + pathLen, leaf);
232 			}
233 		}
234 		// check, if necessary to normalize
235 		if (error == B_OK && !normalize)
236 			normalize = normalize || _MustNormalize(newPath, &error);
237 
238 		// normalize the path, if necessary, otherwise just set it
239 		if (error == B_OK) {
240 			if (normalize) {
241 				// create a BEntry and initialize us with this entry
242 				BEntry entry;
243 				error = entry.SetTo(newPath, false);
244 				if (error == B_OK)
245 					return SetTo(&entry);
246 			} else
247 				error = _SetPath(newPath);
248 		}
249 	}
250 	// cleanup, if something went wrong
251 	if (error != B_OK)
252 		Unset();
253 	fCStatus = error;
254 	return error;
255 }
256 
257 
258 /*!	\brief Reinitializes the object to the specified directory and relative
259 	path combination.
260 	\param dir Refers to the directory that provides the base component of the
261 		   pathname.
262 	\param path the relative path name (may be \c NULL)
263 	\param normalize boolean flag used to force normalization; normalization
264 		   may occur even if false (see \ref _MustNormalize).
265 	\return
266 	- \c B_OK: The initialization was successful.
267 	- \c B_BAD_VALUE: \c NULL \a dir or absolute \a path.
268 	- \c B_NAME_TOO_LONG: The pathname is longer than \c B_PATH_NAME_LENGTH.
269 	- other error codes.
270 */
271 status_t
272 BPath::SetTo(const BDirectory* dir, const char* path, bool normalize)
273 {
274 	status_t error = (dir && dir->InitCheck() == B_OK ? B_OK : B_BAD_VALUE);
275 	// get the path of the BDirectory
276 	BEntry entry;
277 	if (error == B_OK)
278 		error = dir->GetEntry(&entry);
279 	BPath dirPath;
280 	if (error == B_OK)
281 		error = dirPath.SetTo(&entry);
282 	// let the other version do the work
283 	if (error == B_OK)
284 		error = SetTo(dirPath.Path(), path, normalize);
285 	if (error != B_OK)
286 		Unset();
287 	fCStatus = error;
288 	return error;
289 }
290 
291 
292 /*!	\brief Returns the object to an uninitialized state. The object frees any
293 	resources it allocated and marks itself as uninitialized.
294 */
295 void
296 BPath::Unset()
297 {
298 	_SetPath(NULL);
299 	fCStatus = B_NO_INIT;
300 }
301 
302 
303 /*!	\brief Appends the given (relative) path to the end of the current path.
304 	This call fails if the path is absolute or the object to which you're
305 	appending is uninitialized.
306 	\param path relative pathname to append to current path (may be \c NULL).
307 	\param normalize boolean flag used to force normalization; normalization
308 		   may occur even if false (see \ref _MustNormalize).
309 	\return
310 	- \c B_OK: The initialization was successful.
311 	- \c B_BAD_VALUE: The object is not properly initialized.
312 	- \c B_NAME_TOO_LONG: The pathname is longer than \c B_PATH_NAME_LENGTH.
313 	- other error codes.
314 */
315 status_t
316 BPath::Append(const char* path, bool normalize)
317 {
318 	status_t error = (InitCheck() == B_OK ? B_OK : B_BAD_VALUE);
319 	if (error == B_OK)
320 		error = SetTo(Path(), path, normalize);
321 	if (error != B_OK)
322 		Unset();
323 	fCStatus = error;
324 	return error;
325 }
326 
327 
328 /*! \brief Returns the object's complete path name.
329 	\return
330 	- the object's path name, or
331 	- \c NULL, if it is not properly initialized.
332 */
333 const char*
334 BPath::Path() const
335 {
336 	return fName;
337 }
338 
339 
340 /*! \brief Returns the leaf portion of the object's path name.
341 	The leaf portion is defined as the string after the last \c '/'. For
342 	the root path (\c "/") it is the empty string (\c "").
343 	\return
344 	- the leaf portion of the object's path name, or
345 	- \c NULL, if it is not properly initialized.
346 */
347 const char*
348 BPath::Leaf() const
349 {
350 	if (InitCheck() != B_OK)
351 		return NULL;
352 
353 	const char* result = fName + strlen(fName);
354 	// There should be no need for the second condition, since we deal
355 	// with absolute paths only and those contain at least one '/'.
356 	// However, it doesn't harm.
357 	while (*result != '/' && result > fName)
358 		result--;
359 	result++;
360 
361 	return result;
362 }
363 
364 
365 /*! \brief Calls the argument's SetTo() method with the name of the
366 	object's parent directory.
367 	No normalization is done.
368 	\param path the BPath object to be initialized to the parent directory's
369 		   path name.
370 	\return
371 	- \c B_OK: Everything went fine.
372 	- \c B_BAD_VALUE: \c NULL \a path.
373 	- \c B_ENTRY_NOT_FOUND: The object represents \c "/".
374 	- other error code returned by SetTo().
375 */
376 status_t
377 BPath::GetParent(BPath* path) const
378 {
379 	if (path == NULL)
380 		return B_BAD_VALUE;
381 
382 	status_t error = InitCheck();
383 	if (error != B_OK)
384 		return error;
385 
386 	int32 length = strlen(fName);
387 	if (length == 1) {
388 		// handle "/" (path is supposed to be absolute)
389 		return B_ENTRY_NOT_FOUND;
390 	}
391 
392 	char parentPath[B_PATH_NAME_LENGTH];
393 	length--;
394 	while (fName[length] != '/' && length > 0)
395 		length--;
396 	if (length == 0) {
397 		// parent dir is "/"
398 		length++;
399 	}
400 	memcpy(parentPath, fName, length);
401 	parentPath[length] = '\0';
402 
403 	return path->SetTo(parentPath);
404 }
405 
406 
407 bool
408 BPath::IsAbsolute() const
409 {
410 	if (InitCheck() != B_OK)
411 		return false;
412 
413 	return fName[0] == '/';
414 }
415 
416 
417 /*! \brief Performs a simple (string-wise) comparison of paths.
418 	No normalization takes place! Uninitialized BPath objects are considered
419 	to be equal.
420 	\param item the BPath object to be compared with
421 	\return \c true, if the path names are equal, \c false otherwise.
422 */
423 bool
424 BPath::operator==(const BPath& item) const
425 {
426 	return *this == item.Path();
427 }
428 
429 
430 /*! \brief Performs a simple (string-wise) comparison of paths.
431 	No normalization takes place!
432 	\param path the path name to be compared with
433 	\return \c true, if the path names are equal, \c false otherwise.
434 */
435 bool
436 BPath::operator==(const char* path) const
437 {
438 	return (InitCheck() != B_OK && path == NULL)
439 		|| (fName != NULL && path != NULL && strcmp(fName, path) == 0);
440 }
441 
442 
443 /*! \brief Performs a simple (string-wise) comparison of paths.
444 	No normalization takes place! Uninitialized BPath objects are considered
445 	to be equal.
446 	\param item the BPath object to be compared with
447 	\return \c true, if the path names are not equal, \c false otherwise.
448 */
449 bool
450 BPath::operator!=(const BPath& item) const
451 {
452 	return !(*this == item);
453 }
454 
455 
456 /*! \brief Performs a simple (string-wise) comparison of paths.
457 	No normalization takes place!
458 	\param path the path name to be compared with
459 	\return \c true, if the path names are not equal, \c false otherwise.
460 */
461 bool
462 BPath::operator!=(const char* path) const
463 {
464 	return !(*this == path);
465 }
466 
467 
468 /*! \brief Initializes the object to be a copy of the argument.
469 	\param item the BPath object to be copied
470 	\return \c *this
471 */
472 BPath&
473 BPath::operator=(const BPath& item)
474 {
475 	if (this != &item)
476 		*this = item.Path();
477 	return *this;
478 }
479 
480 
481 /*! \brief Initializes the object to be a copy of the argument.
482 	Has the same effect as \code SetTo(path) \endcode.
483 	\param path the path name to be assigned to this object
484 	\return \c *this
485 */
486 BPath&
487 BPath::operator=(const char* path)
488 {
489 	if (path == NULL)
490 		Unset();
491 	else
492 		SetTo(path);
493 	return *this;
494 }
495 
496 
497 //	#pragma mark - BFlattenable functionality
498 
499 
500 // that's the layout of a flattened entry_ref
501 struct flattened_entry_ref {
502 	dev_t device;
503 	ino_t directory;
504 	char name[1];
505 };
506 
507 // base size of a flattened entry ref
508 static const size_t flattened_entry_ref_size
509 	= sizeof(dev_t) + sizeof(ino_t);
510 
511 
512 /*! \brief Returns \c false.
513 	Implements BFlattenable.
514 	\return \c false
515 */
516 bool
517 BPath::IsFixedSize() const
518 {
519 	return false;
520 }
521 
522 
523 /*! \brief Returns \c B_REF_TYPE.
524 	Implements BFlattenable.
525 	\return \c B_REF_TYPE
526 */
527 type_code
528 BPath::TypeCode() const
529 {
530 	return B_REF_TYPE;
531 }
532 
533 
534 /*!	\brief Returns the size of the flattened entry_ref structure that
535 	represents the pathname.
536 	Implements BFlattenable.
537 	\return the size needed for flattening.
538 */
539 ssize_t
540 BPath::FlattenedSize() const
541 {
542 	ssize_t size = flattened_entry_ref_size;
543 	BEntry entry;
544 	entry_ref ref;
545 	if (InitCheck() == B_OK
546 		&& entry.SetTo(Path()) == B_OK
547 		&& entry.GetRef(&ref) == B_OK) {
548 		size += strlen(ref.name) + 1;
549 	}
550 	return size;
551 }
552 
553 
554 /*!	\brief Converts the object's pathname to an entry_ref and writes it into
555 	buffer.
556 	Implements BFlattenable.
557 	\param buffer the buffer the data shall be stored in
558 	\param size the size of \a buffer
559 	\return
560 	- \c B_OK: Everything went fine.
561 	- \c B_BAD_VALUE: \c NULL buffer or the buffer is of insufficient size.
562 	- other error codes.
563 	\todo Reimplement for performance reasons: Don't call FlattenedSize().
564 */
565 status_t
566 BPath::Flatten(void* buffer, ssize_t size) const
567 {
568 	if (buffer == NULL)
569 		return B_BAD_VALUE;
570 
571 	ssize_t flattenedSize = FlattenedSize();
572 	if (flattenedSize < 0)
573 		return flattenedSize;
574 	if (size < flattenedSize)
575 		return B_BAD_VALUE;
576 	status_t status = InitCheck();
577 	if (status != B_OK)
578 		return status;
579 
580 	// convert the path to an entry_ref
581 	BEntry entry;
582 	entry_ref ref;
583 	status = entry.SetTo(Path());
584 	if (status == B_OK)
585 		status = entry.GetRef(&ref);
586 	if (status != B_OK)
587 		return status;
588 
589 	// store the entry_ref in the buffer
590 	flattened_entry_ref& fref = *(flattened_entry_ref*)buffer;
591 	fref.device = ref.device;
592 	fref.directory = ref.directory;
593 	if (ref.name)
594 		strcpy(fref.name, ref.name);
595 
596 	return B_OK;
597 }
598 
599 
600 /*! \brief Returns \c true if code is \c B_REF_TYPE, and false otherwise.
601 	Implements BFlattenable.
602 	\param code the type code in question
603 	\return \c true if code is \c B_REF_TYPE, and false otherwise.
604 */
605 bool
606 BPath::AllowsTypeCode(type_code code) const
607 {
608 	return code == B_REF_TYPE;
609 }
610 
611 
612 /*!	\brief Initializes the BPath with the flattened entry_ref data that's
613 	found in the supplied buffer.
614 	The type code must be \c B_REF_TYPE.
615 	Implements BFlattenable.
616 	\param code the type code of the flattened data
617 	\param buf a pointer to the flattened data
618 	\param size the number of bytes contained in \a buf
619 	\return
620 	- \c B_OK: Everything went fine.
621 	- \c B_BAD_VALUE: \c NULL buffer or the buffer doesn't contain an
622 	  entry_ref.
623 	- other error codes.
624 */
625 status_t
626 BPath::Unflatten(type_code code, const void* buffer, ssize_t size)
627 {
628 	Unset();
629 	status_t error = B_OK;
630 	// check params
631 	if (!(code == B_REF_TYPE && buffer != NULL
632 		  && size >= (ssize_t)flattened_entry_ref_size)) {
633 		error = B_BAD_VALUE;
634 	}
635 	if (error == B_OK) {
636 		if (size == (ssize_t)flattened_entry_ref_size) {
637 			// already Unset();
638 		} else {
639 			// reconstruct the entry_ref from the buffer
640 			const flattened_entry_ref& fref
641 				= *(const flattened_entry_ref*)buffer;
642 			BString name(fref.name, size - flattened_entry_ref_size);
643 			entry_ref ref(fref.device, fref.directory, name.String());
644 			error = SetTo(&ref);
645 		}
646 	}
647 	if (error != B_OK)
648 		fCStatus = error;
649 	return error;
650 }
651 
652 
653 void BPath::_WarPath1() {}
654 void BPath::_WarPath2() {}
655 void BPath::_WarPath3() {}
656 
657 
658 /*!	\brief Sets the supplied path.
659 	The path is copied. If \c NULL, the object's path is set to NULL as well.
660 	The object's old path is deleted.
661 	\param path the path to be set
662 	\return
663 	- \c B_OK: Everything went fine.
664 	- \c B_NO_MEMORY: Insufficient memory.
665 */
666 status_t
667 BPath::_SetPath(const char* path)
668 {
669 	status_t error = B_OK;
670 	const char* oldPath = fName;
671 	// set the new path
672 	if (path) {
673 		fName = new(nothrow) char[strlen(path) + 1];
674 		if (fName)
675 			strcpy(fName, path);
676 		else
677 			error = B_NO_MEMORY;
678 	} else
679 		fName = NULL;
680 
681 	// delete the old one
682 	delete[] oldPath;
683 	return error;
684 }
685 
686 
687 /*! \brief Checks a path to see if normalization is required.
688 
689 	The following items require normalization:
690 		- Relative pathnames (after concatenation; e.g. "boot/ltj")
691 		- The presence of "." or ".." ("/boot/ltj/../ltj/./gwar")
692 		- Redundant slashes ("/boot//ltj")
693 		- A trailing slash ("/boot/ltj/")
694 
695 	\param _error A pointer to an error variable that will be set if the input
696 		is not a valid path.
697 	\return
698 		- \c true: \a path requires normalization
699 		- \c false: \a path does not require normalization
700 */
701 bool
702 BPath::_MustNormalize(const char* path, status_t* _error)
703 {
704 	// Check for useless input
705 	if (path == NULL || path[0] == 0) {
706 		if (_error != NULL)
707 			*_error = B_BAD_VALUE;
708 		return false;
709 	}
710 
711 	int len = strlen(path);
712 
713 	/* Look for anything in the string that forces us to normalize:
714 			+ No leading /
715 			+ any occurence of /./ or /../ or //, or a trailing /. or /..
716 			+ a trailing /
717 	*/;
718 	if (path[0] != '/')
719 		return true;	//	not "/*"
720 	else if (len == 1)
721 		return false;	//	"/"
722 	else if (len > 1 && path[len-1] == '/')
723 		return true;	// 	"*/"
724 	else {
725 		enum ParseState {
726 			NoMatch,
727 			InitialSlash,
728 			OneDot,
729 			TwoDots
730 		} state = NoMatch;
731 
732 		for (int i = 0; path[i] != 0; i++) {
733 			switch (state) {
734 				case NoMatch:
735 					if (path[i] == '/')
736 						state = InitialSlash;
737 					break;
738 
739 				case InitialSlash:
740 					if (path[i] == '/')
741 						return true;		// "*//*"
742 
743 					if (path[i] == '.')
744 						state = OneDot;
745 					else
746 						state = NoMatch;
747 					break;
748 
749 				case OneDot:
750 					if (path[i] == '/')
751 						return true;		// "*/./*"
752 
753 					if (path[i] == '.')
754 						state = TwoDots;
755 					else
756 						state = NoMatch;
757 					break;
758 
759 				case TwoDots:
760 					if (path[i] == '/')
761 						return true;		// "*/../*"
762 
763 					state = NoMatch;
764 					break;
765 			}
766 		}
767 
768 		// If we hit the end of the string while in either
769 		// of these two states, there was a trailing /. or /..
770 		if (state == OneDot || state == TwoDots)
771 			return true;
772 
773 		return false;
774 	}
775 }
776 
777 
778 /*!
779 	\var char *BPath::fName
780 	\brief Pointer to the object's path name string.
781 */
782 
783 /*!
784 	\var status_t BPath::fCStatus
785 	\brief The object's initialization status.
786 */
787