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