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