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