headers/bsd: Add sys/queue.h.
[haiku.git] / src / kits / storage / Path.cpp
blob3c47d4f5bf9e864b558c09393b6e0156a508c3ce
1 /*
2 * Copyright 2002-2012, Haiku Inc.
3 * Distributed under the terms of the MIT License.
5 * Authors:
6 * Tyler Dauwalder
7 * Axel Dörfler, axeld@pinc-software.de
8 * Ingo Weinhold, bonefish@users.sf.net
9 */
12 #include <Path.h>
14 #include <new>
16 #include <Directory.h>
17 #include <Entry.h>
18 #include <StorageDefs.h>
19 #include <String.h>
21 #include <syscalls.h>
23 #include "storage_support.h"
26 using namespace std;
29 // Creates an uninitialized BPath object.
30 BPath::BPath()
32 fName(NULL),
33 fCStatus(B_NO_INIT)
38 // Creates a copy of the given BPath object.
39 BPath::BPath(const BPath& path)
41 fName(NULL),
42 fCStatus(B_NO_INIT)
44 *this = path;
48 // Creates a BPath object and initializes it to the filesystem entry
49 // specified by the passed in entry_ref struct.
50 BPath::BPath(const entry_ref* ref)
52 fName(NULL),
53 fCStatus(B_NO_INIT)
55 SetTo(ref);
59 // Creates a BPath object and initializes it to the filesystem entry
60 // specified by the passed in BEntry object.
61 BPath::BPath(const BEntry* entry)
63 fName(NULL),
64 fCStatus(B_NO_INIT)
66 SetTo(entry);
70 // Creates a BPath object and initializes it to the specified path or
71 // path and filename combination.
72 BPath::BPath(const char* dir, const char* leaf, bool normalize)
74 fName(NULL),
75 fCStatus(B_NO_INIT)
77 SetTo(dir, leaf, normalize);
81 // Creates a BPath object and initializes it to the specified directory
82 // and filename combination.
83 BPath::BPath(const BDirectory* dir, const char* leaf, bool normalize)
85 fName(NULL),
86 fCStatus(B_NO_INIT)
88 SetTo(dir, leaf, normalize);
92 // Destroys the BPath object and frees any of its associated resources.
93 BPath::~BPath()
95 Unset();
99 // Checks whether or not the object was properly initialized.
100 status_t
101 BPath::InitCheck() const
103 return fCStatus;
107 // Reinitializes the object to the filesystem entry specified by the
108 // passed in entry_ref struct.
109 status_t
110 BPath::SetTo(const entry_ref* ref)
112 Unset();
113 if (!ref)
114 return fCStatus = B_BAD_VALUE;
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;
122 fCStatus = _SetPath(path);
123 // the path is already normalized
124 return fCStatus;
128 // Reinitializes the object to the specified filesystem entry.
129 status_t
130 BPath::SetTo(const BEntry* entry)
132 Unset();
133 if (entry == NULL)
134 return B_BAD_VALUE;
136 entry_ref ref;
137 fCStatus = entry->GetRef(&ref);
138 if (fCStatus == B_OK)
139 fCStatus = SetTo(&ref);
141 return fCStatus;
145 // Reinitializes the object to the passed in path or path and
146 // leaf combination.
147 status_t
148 BPath::SetTo(const char* path, const char* leaf, bool normalize)
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++;
176 strcpy(newPath + pathLen, leaf);
179 // check, if necessary to normalize
180 if (error == B_OK && !normalize)
181 normalize = normalize || _MustNormalize(newPath, &error);
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);
195 // cleanup, if something went wrong
196 if (error != B_OK)
197 Unset();
198 fCStatus = error;
199 return error;
203 // Reinitializes the object to the passed in dir and relative path combination.
204 status_t
205 BPath::SetTo(const BDirectory* dir, const char* path, bool normalize)
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;
225 // Returns the object to an uninitialized state.
226 void
227 BPath::Unset()
229 _SetPath(NULL);
230 fCStatus = B_NO_INIT;
234 // Appends the passed in relative path to the end of the current path.
235 status_t
236 BPath::Append(const char* path, bool normalize)
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;
248 // Gets the entire path of the object.
249 const char*
250 BPath::Path() const
252 return fName;
256 // Gets the leaf portion of the path.
257 const char*
258 BPath::Leaf() const
260 if (InitCheck() != B_OK)
261 return NULL;
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++;
271 return result;
275 // Initializes path with the parent directory of the BPath object.
276 status_t
277 BPath::GetParent(BPath* path) const
279 if (path == NULL)
280 return B_BAD_VALUE;
282 status_t error = InitCheck();
283 if (error != B_OK)
284 return error;
286 int32 length = strlen(fName);
287 if (length == 1) {
288 // handle "/" (path is supposed to be absolute)
289 return B_ENTRY_NOT_FOUND;
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++;
300 memcpy(parentPath, fName, length);
301 parentPath[length] = '\0';
303 return path->SetTo(parentPath);
307 // Gets whether or not the path is absolute or relative.
308 bool
309 BPath::IsAbsolute() const
311 if (InitCheck() != B_OK)
312 return false;
314 return fName[0] == '/';
318 // Performs a simple (string-wise) comparison of paths for equality.
319 bool
320 BPath::operator==(const BPath& item) const
322 return *this == item.Path();
326 // Performs a simple (string-wise) comparison of paths for equality.
327 bool
328 BPath::operator==(const char* path) const
330 return (InitCheck() != B_OK && path == NULL)
331 || (fName != NULL && path != NULL && strcmp(fName, path) == 0);
335 // Performs a simple (string-wise) comparison of paths for inequality.
336 bool
337 BPath::operator!=(const BPath& item) const
339 return !(*this == item);
343 // Performs a simple (string-wise) comparison of paths for inequality.
344 bool
345 BPath::operator!=(const char* path) const
347 return !(*this == path);
351 // Initializes the object as a copy of item.
352 BPath&
353 BPath::operator=(const BPath& item)
355 if (this != &item)
356 *this = item.Path();
357 return *this;
361 // Initializes the object with the passed in path.
362 BPath&
363 BPath::operator=(const char* path)
365 if (path == NULL)
366 Unset();
367 else
368 SetTo(path);
369 return *this;
373 // #pragma mark - BFlattenable functionality
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];
383 // base size of a flattened entry ref
384 static const size_t flattened_entry_ref_size
385 = sizeof(dev_t) + sizeof(ino_t);
388 // Overrides BFlattenable::IsFixedSize()
389 bool
390 BPath::IsFixedSize() const
392 return false;
396 // Overrides BFlattenable::TypeCode()
397 type_code
398 BPath::TypeCode() const
400 return B_REF_TYPE;
404 // Gets the size of the flattened entry_ref struct that represents
405 // the path in bytes.
406 ssize_t
407 BPath::FlattenedSize() const
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;
417 return size;
421 // Converts the path of the object to an entry_ref and writes it into buffer.
422 status_t
423 BPath::Flatten(void* buffer, ssize_t size) const
425 if (buffer == NULL)
426 return B_BAD_VALUE;
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 status_t status = InitCheck();
435 if (status != B_OK)
436 return status;
438 // convert the path to an entry_ref
439 BEntry entry;
440 entry_ref ref;
441 status = entry.SetTo(Path());
442 if (status == B_OK)
443 status = entry.GetRef(&ref);
444 if (status != B_OK)
445 return status;
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);
454 return B_OK;
458 // Checks if type code is equal to B_REF_TYPE.
459 bool
460 BPath::AllowsTypeCode(type_code code) const
462 return code == B_REF_TYPE;
466 // Initializes the object with the flattened entry_ref data from the passed
467 // in buffer.
468 status_t
469 BPath::Unflatten(type_code code, const void* buffer, ssize_t size)
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;
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);
490 if (error != B_OK)
491 fCStatus = error;
492 return error;
496 void BPath::_WarPath1() {}
497 void BPath::_WarPath2() {}
498 void BPath::_WarPath3() {}
501 /*! Sets the supplied path.
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.
506 \param path the path to be set
508 \returns A status code.
509 \retval B_OK Everything went fine.
510 \retval B_NO_MEMORY Insufficient memory.
512 status_t
513 BPath::_SetPath(const char* path)
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;
527 // delete the old one
528 delete[] oldPath;
529 return error;
533 /*! Checks a path to see if normalization is required.
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/")
541 \param _error A pointer to an error variable that will be set if the input
542 is not a valid path.
544 \return \c true if \a path requires normalization, \c false otherwise.
546 bool
547 BPath::_MustNormalize(const char* path, status_t* _error)
549 // Check for useless input
550 if (path == NULL || path[0] == 0) {
551 if (_error != NULL)
552 *_error = B_BAD_VALUE;
553 return false;
556 int len = strlen(path);
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 /
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;
577 for (int i = 0; path[i] != 0; i++) {
578 switch (state) {
579 case NoMatch:
580 if (path[i] == '/')
581 state = InitialSlash;
582 break;
584 case InitialSlash:
585 if (path[i] == '/')
586 return true; // "*//*"
588 if (path[i] == '.')
589 state = OneDot;
590 else
591 state = NoMatch;
592 break;
594 case OneDot:
595 if (path[i] == '/')
596 return true; // "*/./*"
598 if (path[i] == '.')
599 state = TwoDots;
600 else
601 state = NoMatch;
602 break;
604 case TwoDots:
605 if (path[i] == '/')
606 return true; // "*/../*"
608 state = NoMatch;
609 break;
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;
618 return false;