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
26using namespace std;
27
28
29// Creates an uninitialized BPath object.
30BPath::BPath()
31	:
32	fName(NULL),
33	fCStatus(B_NO_INIT)
34{
35}
36
37
38// Creates a copy of the given BPath object.
39BPath::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.
50BPath::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.
61BPath::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.
72BPath::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.
83BPath::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.
93BPath::~BPath()
94{
95	Unset();
96}
97
98
99// Checks whether or not the object was properly initialized.
100status_t
101BPath::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.
109status_t
110BPath::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.
129status_t
130BPath::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.
147status_t
148BPath::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.
204status_t
205BPath::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.
226void
227BPath::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.
235status_t
236BPath::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.
249const char*
250BPath::Path() const
251{
252	return fName;
253}
254
255
256// Gets the leaf portion of the path.
257const char*
258BPath::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.
276status_t
277BPath::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.
308bool
309BPath::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.
319bool
320BPath::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.
327bool
328BPath::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.
336bool
337BPath::operator!=(const BPath& item) const
338{
339	return !(*this == item);
340}
341
342
343// Performs a simple (string-wise) comparison of paths for inequality.
344bool
345BPath::operator!=(const char* path) const
346{
347	return !(*this == path);
348}
349
350
351// Initializes the object as a copy of item.
352BPath&
353BPath::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.
362BPath&
363BPath::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
377struct 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
384static const size_t flattened_entry_ref_size
385	= sizeof(dev_t) + sizeof(ino_t);
386
387
388// Overrides BFlattenable::IsFixedSize()
389bool
390BPath::IsFixedSize() const
391{
392	return false;
393}
394
395
396// Overrides BFlattenable::TypeCode()
397type_code
398BPath::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.
406ssize_t
407BPath::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.
422status_t
423BPath::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.
459bool
460BPath::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.
468status_t
469BPath::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
496void BPath::_WarPath1() {}
497void BPath::_WarPath2() {}
498void 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*/
512status_t
513BPath::_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*/
546bool
547BPath::_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