xref: /haiku/src/kits/storage/Path.cpp (revision 654135466f2ad0a080c41ddda423291a14e5fe61)
1 /*
2  * Copyright 2002-2012, Haiku Inc.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Tyler Dauwalder
7  *		Axel Dörfler, axeld@pinc-software.de
8  *		Ingo Weinhold, bonefish@users.sf.net
9  */
10 
11 
12 #include <Path.h>
13 
14 #include <new>
15 
16 #include <Directory.h>
17 #include <Entry.h>
18 #include <StorageDefs.h>
19 #include <String.h>
20 
21 #include <syscalls.h>
22 
23 #include "storage_support.h"
24 
25 
26 using namespace std;
27 
28 
29 // Creates an uninitialized BPath object.
BPath()30 BPath::BPath()
31 	:
32 	fName(NULL),
33 	fCStatus(B_NO_INIT)
34 {
35 }
36 
37 
38 // Creates a copy of the given BPath object.
BPath(const BPath & path)39 BPath::BPath(const BPath& path)
40 	:
41 	fName(NULL),
42 	fCStatus(B_NO_INIT)
43 {
44 	*this = path;
45 }
46 
47 
48 // Creates a BPath object and initializes it to the filesystem entry
49 // specified by the passed in entry_ref struct.
BPath(const entry_ref * ref)50 BPath::BPath(const entry_ref* ref)
51 	:
52 	fName(NULL),
53 	fCStatus(B_NO_INIT)
54 {
55 	SetTo(ref);
56 }
57 
58 
59 // Creates a BPath object and initializes it to the filesystem entry
60 // specified by the passed in BEntry object.
BPath(const BEntry * entry)61 BPath::BPath(const BEntry* entry)
62 	:
63 	fName(NULL),
64 	fCStatus(B_NO_INIT)
65 {
66 	SetTo(entry);
67 }
68 
69 
70 // Creates a BPath object and initializes it to the specified path or
71 // path and filename combination.
BPath(const char * dir,const char * leaf,bool normalize)72 BPath::BPath(const char* dir, const char* leaf, bool normalize)
73 	:
74 	fName(NULL),
75 	fCStatus(B_NO_INIT)
76 {
77 	SetTo(dir, leaf, normalize);
78 }
79 
80 
81 // Creates a BPath object and initializes it to the specified directory
82 // and filename combination.
BPath(const BDirectory * dir,const char * leaf,bool normalize)83 BPath::BPath(const BDirectory* dir, const char* leaf, bool normalize)
84 	:
85 	fName(NULL),
86 	fCStatus(B_NO_INIT)
87 {
88 	SetTo(dir, leaf, normalize);
89 }
90 
91 
92 // Destroys the BPath object and frees any of its associated resources.
~BPath()93 BPath::~BPath()
94 {
95 	Unset();
96 }
97 
98 
99 // Checks whether or not the object was properly initialized.
100 status_t
InitCheck() const101 BPath::InitCheck() const
102 {
103 	return fCStatus;
104 }
105 
106 
107 // Reinitializes the object to the filesystem entry specified by the
108 // passed in entry_ref struct.
109 status_t
SetTo(const entry_ref * ref)110 BPath::SetTo(const entry_ref* ref)
111 {
112 	Unset();
113 	if (!ref)
114 		return fCStatus = B_BAD_VALUE;
115 
116 	char path[B_PATH_NAME_LENGTH];
117 	fCStatus = _kern_entry_ref_to_path(ref->device, ref->directory,
118 		ref->name, path, sizeof(path));
119 	if (fCStatus != B_OK)
120 		return fCStatus;
121 
122 	fCStatus = _SetPath(path);
123 		// the path is already normalized
124 	return fCStatus;
125 }
126 
127 
128 // Reinitializes the object to the specified filesystem entry.
129 status_t
SetTo(const BEntry * entry)130 BPath::SetTo(const BEntry* entry)
131 {
132 	Unset();
133 	if (entry == NULL)
134 		return B_BAD_VALUE;
135 
136 	entry_ref ref;
137 	fCStatus = entry->GetRef(&ref);
138 	if (fCStatus == B_OK)
139 		fCStatus = SetTo(&ref);
140 
141 	return fCStatus;
142 }
143 
144 
145 // Reinitializes the object to the passed in path or path and
146 // leaf combination.
147 status_t
SetTo(const char * path,const char * leaf,bool normalize)148 BPath::SetTo(const char* path, const char* leaf, bool normalize)
149 {
150 	status_t error = (path ? B_OK : B_BAD_VALUE);
151 	if (error == B_OK && leaf && BPrivate::Storage::is_absolute_path(leaf))
152 		error = B_BAD_VALUE;
153 	char newPath[B_PATH_NAME_LENGTH];
154 	if (error == B_OK) {
155 		// we always normalize relative paths
156 		normalize |= !BPrivate::Storage::is_absolute_path(path);
157 		// build a new path from path and leaf
158 		// copy path first
159 		uint32 pathLen = strlen(path);
160 		if (pathLen >= sizeof(newPath))
161 			error = B_NAME_TOO_LONG;
162 		if (error == B_OK)
163 			strcpy(newPath, path);
164 		// append leaf, if supplied
165 		if (error == B_OK && leaf) {
166 			bool needsSeparator = (pathLen > 0 && path[pathLen - 1] != '/');
167 			uint32 wholeLen = pathLen + (needsSeparator ? 1 : 0)
168 							  + strlen(leaf);
169 			if (wholeLen >= sizeof(newPath))
170 				error = B_NAME_TOO_LONG;
171 			if (error == B_OK) {
172 				if (needsSeparator) {
173 					newPath[pathLen] = '/';
174 					pathLen++;
175 				}
176 				strcpy(newPath + pathLen, leaf);
177 			}
178 		}
179 		// check, if necessary to normalize
180 		if (error == B_OK && !normalize)
181 			normalize = _MustNormalize(newPath, &error);
182 
183 		// normalize the path, if necessary, otherwise just set it
184 		if (error == B_OK) {
185 			if (normalize) {
186 				// create a BEntry and initialize us with this entry
187 				BEntry entry;
188 				error = entry.SetTo(newPath, false);
189 				if (error == B_OK)
190 					return SetTo(&entry);
191 			} else
192 				error = _SetPath(newPath);
193 		}
194 	}
195 	// cleanup, if something went wrong
196 	if (error != B_OK)
197 		Unset();
198 	fCStatus = error;
199 	return error;
200 }
201 
202 
203 // Reinitializes the object to the passed in dir and relative path combination.
204 status_t
SetTo(const BDirectory * dir,const char * path,bool normalize)205 BPath::SetTo(const BDirectory* dir, const char* path, bool normalize)
206 {
207 	status_t error = (dir && dir->InitCheck() == B_OK ? B_OK : B_BAD_VALUE);
208 	// get the path of the BDirectory
209 	BEntry entry;
210 	if (error == B_OK)
211 		error = dir->GetEntry(&entry);
212 	BPath dirPath;
213 	if (error == B_OK)
214 		error = dirPath.SetTo(&entry);
215 	// let the other version do the work
216 	if (error == B_OK)
217 		error = SetTo(dirPath.Path(), path, normalize);
218 	if (error != B_OK)
219 		Unset();
220 	fCStatus = error;
221 	return error;
222 }
223 
224 
225 // Returns the object to an uninitialized state.
226 void
Unset()227 BPath::Unset()
228 {
229 	_SetPath(NULL);
230 	fCStatus = B_NO_INIT;
231 }
232 
233 
234 // Appends the passed in relative path to the end of the current path.
235 status_t
Append(const char * path,bool normalize)236 BPath::Append(const char* path, bool normalize)
237 {
238 	status_t error = (InitCheck() == B_OK ? B_OK : B_BAD_VALUE);
239 	if (error == B_OK)
240 		error = SetTo(Path(), path, normalize);
241 	if (error != B_OK)
242 		Unset();
243 	fCStatus = error;
244 	return error;
245 }
246 
247 
248 // Gets the entire path of the object.
249 const char*
Path() const250 BPath::Path() const
251 {
252 	return fName;
253 }
254 
255 
256 // Gets the leaf portion of the path.
257 const char*
Leaf() const258 BPath::Leaf() const
259 {
260 	if (InitCheck() != B_OK)
261 		return NULL;
262 
263 	const char* result = fName + strlen(fName);
264 	// There should be no need for the second condition, since we deal
265 	// with absolute paths only and those contain at least one '/'.
266 	// However, it doesn't harm.
267 	while (*result != '/' && result > fName)
268 		result--;
269 	result++;
270 
271 	return result;
272 }
273 
274 
275 // Initializes path with the parent directory of the BPath object.
276 status_t
GetParent(BPath * path) const277 BPath::GetParent(BPath* path) const
278 {
279 	if (path == NULL)
280 		return B_BAD_VALUE;
281 
282 	status_t error = InitCheck();
283 	if (error != B_OK)
284 		return error;
285 
286 	int32 length = strlen(fName);
287 	if (length == 1) {
288 		// handle "/" (path is supposed to be absolute)
289 		return B_ENTRY_NOT_FOUND;
290 	}
291 
292 	char parentPath[B_PATH_NAME_LENGTH];
293 	length--;
294 	while (fName[length] != '/' && length > 0)
295 		length--;
296 	if (length == 0) {
297 		// parent dir is "/"
298 		length++;
299 	}
300 	memcpy(parentPath, fName, length);
301 	parentPath[length] = '\0';
302 
303 	return path->SetTo(parentPath);
304 }
305 
306 
307 // Gets whether or not the path is absolute or relative.
308 bool
IsAbsolute() const309 BPath::IsAbsolute() const
310 {
311 	if (InitCheck() != B_OK)
312 		return false;
313 
314 	return fName[0] == '/';
315 }
316 
317 
318 // Performs a simple (string-wise) comparison of paths for equality.
319 bool
operator ==(const BPath & item) const320 BPath::operator==(const BPath& item) const
321 {
322 	return *this == item.Path();
323 }
324 
325 
326 // Performs a simple (string-wise) comparison of paths for equality.
327 bool
operator ==(const char * path) const328 BPath::operator==(const char* path) const
329 {
330 	return (InitCheck() != B_OK && path == NULL)
331 		|| (fName != NULL && path != NULL && strcmp(fName, path) == 0);
332 }
333 
334 
335 // Performs a simple (string-wise) comparison of paths for inequality.
336 bool
operator !=(const BPath & item) const337 BPath::operator!=(const BPath& item) const
338 {
339 	return !(*this == item);
340 }
341 
342 
343 // Performs a simple (string-wise) comparison of paths for inequality.
344 bool
operator !=(const char * path) const345 BPath::operator!=(const char* path) const
346 {
347 	return !(*this == path);
348 }
349 
350 
351 // Initializes the object as a copy of item.
352 BPath&
operator =(const BPath & item)353 BPath::operator=(const BPath& item)
354 {
355 	if (this != &item)
356 		*this = item.Path();
357 	return *this;
358 }
359 
360 
361 // Initializes the object with the passed in path.
362 BPath&
operator =(const char * path)363 BPath::operator=(const char* path)
364 {
365 	if (path == NULL)
366 		Unset();
367 	else
368 		SetTo(path);
369 	return *this;
370 }
371 
372 
373 //	#pragma mark - BFlattenable functionality
374 
375 
376 // that's the layout of a flattened entry_ref
377 struct flattened_entry_ref {
378 	dev_t device;
379 	ino_t directory;
380 	char name[1];
381 };
382 
383 // base size of a flattened entry ref
384 static const size_t flattened_entry_ref_size
385 	= sizeof(dev_t) + sizeof(ino_t);
386 
387 
388 // Overrides BFlattenable::IsFixedSize()
389 bool
IsFixedSize() const390 BPath::IsFixedSize() const
391 {
392 	return false;
393 }
394 
395 
396 // Overrides BFlattenable::TypeCode()
397 type_code
TypeCode() const398 BPath::TypeCode() const
399 {
400 	return B_REF_TYPE;
401 }
402 
403 
404 // Gets the size of the flattened entry_ref struct that represents
405 // the path in bytes.
406 ssize_t
FlattenedSize() const407 BPath::FlattenedSize() const
408 {
409 	ssize_t size = flattened_entry_ref_size;
410 	BEntry entry;
411 	entry_ref ref;
412 	if (InitCheck() == B_OK
413 		&& entry.SetTo(Path()) == B_OK
414 		&& entry.GetRef(&ref) == B_OK) {
415 		size += strlen(ref.name) + 1;
416 	}
417 	return size;
418 }
419 
420 
421 // Converts the path of the object to an entry_ref and writes it into buffer.
422 status_t
Flatten(void * buffer,ssize_t size) const423 BPath::Flatten(void* buffer, ssize_t size) const
424 {
425 	if (buffer == NULL)
426 		return B_BAD_VALUE;
427 
428 	// ToDo: Reimplement for performance reasons: Don't call FlattenedSize().
429 	ssize_t flattenedSize = FlattenedSize();
430 	if (flattenedSize < 0)
431 		return flattenedSize;
432 	if (size < flattenedSize)
433 		return B_BAD_VALUE;
434 
435 	// convert the path to an entry_ref
436 	BEntry entry;
437 	entry_ref ref;
438 
439 	if (Path() != NULL) {
440 		status_t status = entry.SetTo(Path());
441 		if (status == B_OK)
442 			status = entry.GetRef(&ref);
443 		if (status != B_OK)
444 			return status;
445 	}
446 
447 	// store the entry_ref in the buffer
448 	flattened_entry_ref& fref = *(flattened_entry_ref*)buffer;
449 	fref.device = ref.device;
450 	fref.directory = ref.directory;
451 	if (ref.name)
452 		strcpy(fref.name, ref.name);
453 
454 	return B_OK;
455 }
456 
457 
458 // Checks if type code is equal to B_REF_TYPE.
459 bool
AllowsTypeCode(type_code code) const460 BPath::AllowsTypeCode(type_code code) const
461 {
462 	return code == B_REF_TYPE;
463 }
464 
465 
466 // Initializes the object with the flattened entry_ref data from the passed
467 // in buffer.
468 status_t
Unflatten(type_code code,const void * buffer,ssize_t size)469 BPath::Unflatten(type_code code, const void* buffer, ssize_t size)
470 {
471 	Unset();
472 	status_t error = B_OK;
473 	// check params
474 	if (!(code == B_REF_TYPE && buffer != NULL
475 		  && size >= (ssize_t)flattened_entry_ref_size)) {
476 		error = B_BAD_VALUE;
477 	}
478 	if (error == B_OK) {
479 		if (size == (ssize_t)flattened_entry_ref_size) {
480 			// already Unset();
481 		} else {
482 			// reconstruct the entry_ref from the buffer
483 			const flattened_entry_ref& fref
484 				= *(const flattened_entry_ref*)buffer;
485 			BString name(fref.name, size - flattened_entry_ref_size);
486 			entry_ref ref(fref.device, fref.directory, name.String());
487 			error = SetTo(&ref);
488 		}
489 	}
490 	if (error != B_OK)
491 		fCStatus = error;
492 	return error;
493 }
494 
495 
_WarPath1()496 void BPath::_WarPath1() {}
_WarPath2()497 void BPath::_WarPath2() {}
_WarPath3()498 void BPath::_WarPath3() {}
499 
500 
501 /*!	Sets the supplied path.
502 
503 	The path is copied, if \a path is \c NULL the path of the object is set to
504 	\c NULL as well. The old path is deleted.
505 
506 	\param path the path to be set
507 
508 	\returns A status code.
509 	\retval B_OK Everything went fine.
510 	\retval B_NO_MEMORY Insufficient memory.
511 */
512 status_t
_SetPath(const char * path)513 BPath::_SetPath(const char* path)
514 {
515 	status_t error = B_OK;
516 	const char* oldPath = fName;
517 	// set the new path
518 	if (path) {
519 		fName = new(nothrow) char[strlen(path) + 1];
520 		if (fName)
521 			strcpy(fName, path);
522 		else
523 			error = B_NO_MEMORY;
524 	} else
525 		fName = NULL;
526 
527 	// delete the old one
528 	delete[] oldPath;
529 	return error;
530 }
531 
532 
533 /*!	Checks a path to see if normalization is required.
534 
535 	The following items require normalization:
536 	- Relative pathnames (after concatenation; e.g. "boot/ltj")
537 	- The presence of "." or ".." ("/boot/ltj/../ltj/./gwar")
538 	- Redundant slashes ("/boot//ltj")
539 	- A trailing slash ("/boot/ltj/")
540 
541 	\param _error A pointer to an error variable that will be set if the input
542 		is not a valid path.
543 
544 	\return \c true if \a path requires normalization, \c false otherwise.
545 */
546 bool
_MustNormalize(const char * path,status_t * _error)547 BPath::_MustNormalize(const char* path, status_t* _error)
548 {
549 	// Check for useless input
550 	if (path == NULL || path[0] == 0) {
551 		if (_error != NULL)
552 			*_error = B_BAD_VALUE;
553 		return false;
554 	}
555 
556 	int len = strlen(path);
557 
558 	/* Look for anything in the string that forces us to normalize:
559 			+ No leading /
560 			+ any occurence of /./ or /../ or //, or a trailing /. or /..
561 			+ a trailing /
562 	*/;
563 	if (path[0] != '/')
564 		return true;	//	not "/*"
565 	else if (len == 1)
566 		return false;	//	"/"
567 	else if (len > 1 && path[len-1] == '/')
568 		return true;	// 	"*/"
569 	else {
570 		enum ParseState {
571 			NoMatch,
572 			InitialSlash,
573 			OneDot,
574 			TwoDots
575 		} state = NoMatch;
576 
577 		for (int i = 0; path[i] != 0; i++) {
578 			switch (state) {
579 				case NoMatch:
580 					if (path[i] == '/')
581 						state = InitialSlash;
582 					break;
583 
584 				case InitialSlash:
585 					if (path[i] == '/')
586 						return true;		// "*//*"
587 
588 					if (path[i] == '.')
589 						state = OneDot;
590 					else
591 						state = NoMatch;
592 					break;
593 
594 				case OneDot:
595 					if (path[i] == '/')
596 						return true;		// "*/./*"
597 
598 					if (path[i] == '.')
599 						state = TwoDots;
600 					else
601 						state = NoMatch;
602 					break;
603 
604 				case TwoDots:
605 					if (path[i] == '/')
606 						return true;		// "*/../*"
607 
608 					state = NoMatch;
609 					break;
610 			}
611 		}
612 
613 		// If we hit the end of the string while in either
614 		// of these two states, there was a trailing /. or /..
615 		if (state == OneDot || state == TwoDots)
616 			return true;
617 
618 		return false;
619 	}
620 }
621