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