1 /*****************************************************************************\
4 * Author : Chris Koeritz *
6 *******************************************************************************
7 * Copyright (c) 1993-$now By Author. This program is free software; you can *
8 * redistribute it and/or modify it under the terms of the GNU General Public *
9 * License as published by the Free Software Foundation; either version 2 of *
10 * the License or (at your option) any later version. This is online at: *
11 * http://www.fsf.org/copyleft/gpl.html *
12 * Please send any updates to: fred@gruntose.com *
13 \*****************************************************************************/
15 // implementation note: the filename is kept canonicalized. any constructor
16 // or assignment operator should ensure this (except the blank constructor).
20 #include <basis/byte_array.h>
21 #include <basis/functions.h>
22 #include <configuration/application_configuration.h>
24 hmmm: note that we are relying on forward declared code here.
25 the canonical ordering for feisty's nucleus has the filesystem code come before
26 the configuration code, because the configuratin library uses filesystem features.
27 not sure i want to resolve this bizarritude at this time, but it points to the
28 need for a virtual interface at lower level than either filesystem or configuration
29 libraries, so we can emplace the need for the virtual unix root as a low-level
30 dependency to be implemented later.
33 #include <textual/parser_bits.h>
37 #include <sys/types.h>
38 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
45 #define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s())
47 using namespace basis;
48 using namespace structures;
50 class status_info : public stat
54 namespace filesystem {
56 //#if defined(__WIN32__) || defined(__VMS__)
57 // const char DEFAULT_SEPARATOR = '\\';
58 //#elif defined(__UNIX__)
59 const char DEFAULT_SEPARATOR = '/';
61 // #error "We have no idea what the default path separator is."
64 const char *NO_PARENT_DEFAULT = ".";
65 // used when no directory name can be popped off.
72 filename::filename(const astring &name)
77 filename::filename(const astring &directory, const astring &name_of_file)
81 // if the directory is empty, use the current directory.
83 *this = astring(NO_PARENT_DEFAULT);
84 _had_directory = false;
86 // check for a slash on the end of the directory. add one if there is none
88 bool add_slash = false;
89 if ( (directory[directory.end()] != '\\')
90 && (directory[directory.end()] != '/') ) add_slash = true;
91 if (add_slash) *this += DEFAULT_SEPARATOR;
92 *this += name_of_file;
96 filename::filename(const filename &to_copy)
98 _had_directory(to_copy._had_directory)
101 filename::~filename() {}
103 astring filename::default_separator() { return astring(DEFAULT_SEPARATOR, 1); }
105 astring &filename::raw() { return *this; }
107 const astring &filename::raw() const { return *this; }
109 bool filename::good() const { return exists(); }
111 bool filename::unlink() const { return ::unlink(observe()) == 0; }
113 void filename::reset(const astring &name) {
115 _had_directory = true; // until we know better.
119 astring filename::null_device()
128 bool filename::separator(char is_it)
129 { return (is_it == pc_separator) || (is_it == unix_separator); }
131 filename &filename::operator = (const filename &to_copy)
133 if (this == &to_copy) return *this;
134 (astring &)(*this) = to_copy;
135 _had_directory = to_copy._had_directory;
139 filename &filename::operator = (const astring &to_copy)
141 _had_directory = true;
142 if (this == &to_copy) return *this;
143 (astring &)(*this) = to_copy;
148 astring filename::pop()
150 astring to_return = basename();
151 filename parent_dir = parent();
152 if (parent_dir.raw().equal_to(NO_PARENT_DEFAULT)) {
153 // we haven't gone anywhere.
154 return ""; // signal that nothing was removed.
160 filename filename::parent() const { return dirname(); }
162 void filename::push(const astring &to_push)
164 *this = filename(*this, to_push);
167 void filename::canonicalize()
169 FUNCDEF("canonicalize");
170 // turn all the non-default separators into the default.
171 bool found_sep = false;
172 for (int j = 0; j < length(); j++) {
173 if (separator(get(j))) {
175 put(j, DEFAULT_SEPARATOR);
179 // if there wasn't a single directory separator, then they must not have
180 // specified any directory name for this filename (although it could itself
182 if (!found_sep) _had_directory = false;
184 // remove all occurrences of double separators except for the first
185 // double set, which could be a UNC filename. that's why the index below
186 // starts at one rather than zero.
187 bool saw_sep = false;
188 for (int i = 1; i < length(); i++) {
189 if (separator(get(i))) {
192 // two in a row is no good, except for the first two.
193 i--; // skip back one and try again.
197 } else saw_sep = false;
201 // on windows, we want to translate away from any cygwin or msys format into a more palatable
202 // version that the rest of windows understands.
204 //hmmm: make these into statics!
205 const astring CYGDRIVE_SENTINEL = "cygdrive";
206 const astring CYGDRIVE_PATH = astring(astring(DEFAULT_SEPARATOR, 1)
207 + CYGDRIVE_SENTINEL + astring(DEFAULT_SEPARATOR, 1));
209 // must be at least as long as the string we're looking for, plus a drive letter afterwards.
210 if ( (length() >= CYGDRIVE_PATH.length() + 1)
212 && separator(get(CYGDRIVE_PATH.length() - 1))
213 && compare(CYGDRIVE_SENTINEL, 1,
214 0, CYGDRIVE_SENTINEL.length(), true) ) {
215 zap(0, CYGDRIVE_PATH.length() - 1); // whack the cygdrive portion plus two slashes.
216 insert(1, ":"); // add a colon after the imputed drive letter.
217 //LOG(astring("turned cygdrive path string into: ") + *this);
219 //LOG(astring("path didn't match so left as: ") + *this);
222 // now we convert msys...
223 if ( (length() >= 2) && (get(0) == DEFAULT_SEPARATOR)
224 && textual::parser_bits::is_alpha(get(1)) ) {
225 // we seem reasonably sure now that this is a windows path hiding in msys format, but
226 // the next character needs to be a slash (if there is a next character) for it to be
227 // the windows drive form. otherwise it could be /tmp, which would obviously not be
228 // intended as a windows path.
229 if ( (length() == 2) || (get(2) == DEFAULT_SEPARATOR) ) {
230 // cool, this should be interpretable as an msys path, except for those wacky types
231 // of folks that might use a top-level single character directory name. we cannot
232 // help them, because we have made a design decision to support msys-style paths.
233 // note that this would only affect someone if they were referring to their directory on
234 // the current windows partition (c:, d:, etc.) without providing the drive letter,
235 // if they had that single character directory name (e.g., c:\x, d:\q, etc.) and even
236 // then only on the near defunct windows platform.
237 zap(0, 0); // take off initial slash.
238 insert(1, ":"); // add the obligatory colon.
239 //LOG(astring("turned msys string into: ") + *this);
243 // if we still have a unix style path here on windows, then there will be
244 // trouble when we pass that to the OS. we are not using any cygwin or
245 // other virtualization libraries directly, so we can't rely on those to
246 // fix the path. but if we built under something like cygwin, we should
247 // have stored the real dos-style location of the virtual unix root. we
248 // will use that to replace the root '/' and this should fix most of that
250 bool inject_root = false; // assume we don't need to do anything.
252 LOG(astring("before root injection: ") + raw());
254 // condition here just checks if the path is only the root.
255 if ( (length() == 1) && separator(get(0)) ) { inject_root = true; }
257 if (inject_root) LOG("decided to inject root since path is '/'.");
259 // condition is looking for first character being a slash, and second char as alphanumeric or dash or underscore.
260 // we will currently fail detecting freaky paths that don't start off with alphanumeric or one of that small set of special chars.
263 && ( textual::parser_bits::is_alphanumeric(get(1)) || ('-' == get(1)) || ('_' == get(1)) ) ) {
265 if (inject_root) LOG(astring("decided to inject root since path is compatible: ") + *this);
268 LOG(astring("after second phase root injection: ") + *this);
271 // inject the actual path to the unix root in front, if we know it.
272 // if we don't know it, then a default path that's unlikely to work is idiotically plugged in.
273 insert(0, configuration::application_configuration::get_virtual_unix_root());
274 LOG(astring("turned cygdrive path string into: ") + *this);
278 // we don't crop the last separator if the name's too small. for msdos
279 // names, that would be chopping a slash off the c:\ style name.
281 // zap any separators that are hiding on the end.
282 const int last = end();
283 if (separator(get(last))) zap(last, last);
284 } else if ( (length() == 2) && (get(1) == ':') ) {
285 // special case for dos drive names. we turn it back into a valid
286 // directory rather than leaving it as just "X:". that form of the name
287 // means something else under dos/windows.
288 *this += astring(DEFAULT_SEPARATOR, 1);
292 char filename::drive(bool interact_with_fs) const
294 // first guess: if second letter's a colon, first letter's the drive.
299 if (!interact_with_fs)
302 // otherwise, retrieve the file system's record for the file.
304 if (!get_info(&fill))
306 return char('A' + fill.st_dev);
309 astring filename::extension() const
311 astring base(basename().raw());
312 int posn = base.find('.', base.end(), true);
315 return base.substring(posn + 1, base.length() - 1);
318 astring filename::rootname() const
320 astring base(basename().raw());
321 int posn = base.find('.', base.end(), true);
324 return base.substring(0, posn - 1);
327 bool filename::get_info(status_info *to_fill) const
329 int ret = stat(observe(), to_fill);
335 bool filename::is_directory() const
338 if (!get_info(&fill))
340 return !!(fill.st_mode & S_IFDIR);
343 bool filename::is_writable() const
346 if (!get_info(&fill))
348 return !!(fill.st_mode & S_IWRITE);
351 bool filename::is_readable() const
354 if (!get_info(&fill))
356 return !!(fill.st_mode & S_IREAD);
359 bool filename::is_executable() const
362 if (!get_info(&fill))
364 return !!(fill.st_mode & S_IEXEC);
367 bool filename::is_normal() const
370 if (!get_info(&fill))
372 #if defined(__WIN32__) || defined(__VMS__)
373 //hmmm: is there a corresponding set of functions for windows, where applicable?
376 bool weird = S_ISCHR(fill.st_mode)
377 || S_ISBLK(fill.st_mode)
378 || S_ISFIFO(fill.st_mode)
379 || S_ISSOCK(fill.st_mode);
384 int filename::find_last_separator(const astring &look_at) const
389 sep = look_at.find(DEFAULT_SEPARATOR, last_sep + 1);
390 if (sep >= 0) last_sep = sep;
395 filename filename::basename() const
397 astring basename = *this;
398 int last_sep = find_last_separator(basename);
399 if (last_sep >= 0) basename.zap(0, last_sep);
403 filename filename::dirname() const
405 astring dirname = *this;
406 int last_sep = find_last_separator(dirname);
407 // we don't accept ripping off the first slash.
409 // we can rip the slash and suffix off to get the directory name. however,
410 // this might be in the form X: on windows. if they want the slash to
411 // remain, they can use the dirname that appends it.
412 dirname.zap(last_sep, dirname.end());
414 if (get(0) == DEFAULT_SEPARATOR) {
415 // handle when we're up at the top of the filesystem. on unix, once
416 // you hit the root, you can keep going up but you still remain at
417 // the root. similarly on windoze, if there's no drive name in there.
418 dirname = astring(DEFAULT_SEPARATOR, 1);
420 // there's no slash at all in the filename any more. we assume that
421 // the directory is the current one, if no other information is
422 // available. this default is already used by some code.
423 dirname = NO_PARENT_DEFAULT;
429 astring filename::dirname(bool add_slash) const
431 astring tempname = dirname().raw();
432 if (add_slash) tempname += DEFAULT_SEPARATOR;
436 bool filename::exists() const
440 // if the file name is empty, that cannot exist.
443 return is_readable();
446 bool filename::legal_character(char to_check)
451 case '*': case '?': case '$': case '&': case '|':
452 case '\'': case '"': case '`':
458 default: return true;
462 void filename::detooth_filename(astring &to_clean, char replacement)
464 for (int i = 0; i < to_clean.length(); i++) {
465 if (!legal_character(to_clean[i]))
466 to_clean[i] = replacement;
470 int filename::packed_size() const
472 return PACKED_SIZE_INT32 + astring::packed_size();
475 void filename::pack(byte_array &packed_form) const
477 attach(packed_form, int(_had_directory));
478 astring::pack(packed_form);
481 bool filename::unpack(byte_array &packed_form)
484 if (!detach(packed_form, temp))
486 _had_directory = temp;
487 if (!astring::unpack(packed_form))
492 void filename::separate(bool &rooted, string_array &pieces) const
495 const astring &raw_form = raw();
496 astring accumulator; // holds the names we find.
497 rooted = raw_form.length() && separator(raw_form[0]);
498 for (int i = 0; i < raw_form.length(); i++) {
499 if (separator(raw_form[i])) {
500 // this is a separator character, so eat it and add the accumulated
501 // string to the list.
502 if (i && accumulator.length()) pieces += accumulator;
503 // now reset our accumulated text.
504 accumulator = astring::empty_string();
506 // not a separator, so just accumulate it.
507 accumulator += raw_form[i];
510 if (accumulator.length()) pieces += accumulator;
513 void filename::join(bool rooted, const string_array &pieces)
515 astring constructed_name; // we'll make a filename here.
516 if (rooted) constructed_name += DEFAULT_SEPARATOR;
517 for (int i = 0; i < pieces.length(); i++) {
518 constructed_name += pieces[i];
519 if (!i || (i != pieces.length() - 1))
520 constructed_name += DEFAULT_SEPARATOR;
522 *this = constructed_name;
525 bool filename::base_compare_prefix(const filename &to_compare,
526 string_array &first, string_array &second)
529 separate(first_rooted, first);
531 to_compare.separate(second_rooted, second);
532 if (first_rooted != second_rooted) {
535 // that case should never be allowed, since there are some bits missing
536 // in the name to be compared.
537 if (first.length() > second.length())
540 // compare each of the pieces.
541 for (int i = 0; i < first.length(); i++) {
542 #if defined(__WIN32__) || defined(__VMS__)
543 // case-insensitive compare.
544 if (!first[i].iequals(second[i]))
547 // case-sensitive compare.
548 if (first[i] != second[i])
555 bool filename::compare_prefix(const filename &to_compare, astring &sequel)
557 sequel = astring::empty_string(); // clean our output parameter.
560 if (!base_compare_prefix(to_compare, first, second))
563 // create the sequel string.
564 int extra_strings = second.length() - first.length();
565 for (int i = second.length() - extra_strings; i < second.length(); i++) {
567 if (i != second.length() - 1) sequel += DEFAULT_SEPARATOR;
573 bool filename::compare_prefix(const filename &to_compare)
577 return base_compare_prefix(to_compare, first, second);
580 bool filename::base_compare_suffix(const filename &to_compare,
581 string_array &first, string_array &second)
584 separate(first_rooted, first);
586 to_compare.separate(second_rooted, second);
587 // that case should never be allowed, since there are some bits missing
588 // in the name to be compared.
589 if (first.length() > second.length())
592 // compare each of the pieces.
593 for (int i = first.length() - 1; i >= 0; i--) {
594 //clean up this computation; the difference in lengths is constant--use that.
595 int distance_from_end = first.length() - 1 - i;
596 int j = second.length() - 1 - distance_from_end;
597 #if defined(__WIN32__) || defined(__VMS__)
598 // case-insensitive compare.
599 if (!first[i].iequals(second[j]))
602 // case-sensitive compare.
603 if (first[i] != second[j])
610 bool filename::compare_suffix(const filename &to_compare, astring &prequel)
612 prequel = astring::empty_string(); // clean our output parameter.
615 if (!base_compare_suffix(to_compare, first, second))
618 // create the prequel string.
619 int extra_strings = second.length() - first.length();
620 for (int i = 0; i < extra_strings; i++) {
621 prequel += second[i];
622 if (i != second.length() - 1) prequel += DEFAULT_SEPARATOR;
627 bool filename::compare_suffix(const filename &to_compare)
631 return base_compare_suffix(to_compare, first, second);
634 bool filename::chmod(int write_mode, int owner_mode) const
638 if (write_mode & ALLOW_READ) {
639 if (owner_mode & USER_RIGHTS) chmod_value |= S_IRUSR;
640 if (owner_mode & GROUP_RIGHTS) chmod_value |= S_IRGRP;
641 if (owner_mode & OTHER_RIGHTS) chmod_value |= S_IROTH;
643 if (write_mode & ALLOW_WRITE) {
644 if (owner_mode & USER_RIGHTS) chmod_value |= S_IWUSR;
645 if (owner_mode & GROUP_RIGHTS) chmod_value |= S_IWGRP;
646 if (owner_mode & OTHER_RIGHTS) chmod_value |= S_IWOTH;
648 //// chmod_value = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
649 #elif defined(__WIN32__)
650 if (write_mode & ALLOW_READ) {
651 chmod_value |= _S_IREAD;
653 if (write_mode & ALLOW_WRITE) {
654 chmod_value |= _S_IWRITE;
657 #error unsupported OS type currently.
659 int chmod_result = ::chmod(raw().s(), chmod_value);
661 // LOG(astring("there was a problem changing permissions on ") + raw());