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 <textual/parser_bits.h>
26 #include <sys/types.h>
27 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
34 #define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s())
36 using namespace basis;
37 using namespace structures;
39 class status_info : public stat
43 namespace filesystem {
45 #if defined(__WIN32__) || defined(__VMS__)
46 const char DEFAULT_SEPARATOR = '\\';
47 #elif defined(__UNIX__)
48 const char DEFAULT_SEPARATOR = '/';
50 #error "We have no idea what the default path separator is."
53 const char *NO_PARENT_DEFAULT = ".";
54 // used when no directory name can be popped off.
61 filename::filename(const astring &name)
66 filename::filename(const astring &directory, const astring &name_of_file)
70 // if the directory is empty, use the current directory.
72 *this = astring(NO_PARENT_DEFAULT);
73 _had_directory = false;
75 // check for a slash on the end of the directory. add one if there is none
77 bool add_slash = false;
78 if ( (directory[directory.end()] != '\\')
79 && (directory[directory.end()] != '/') ) add_slash = true;
80 if (add_slash) *this += DEFAULT_SEPARATOR;
81 *this += name_of_file;
85 filename::filename(const filename &to_copy)
87 _had_directory(to_copy._had_directory)
90 filename::~filename() {}
92 astring filename::default_separator() { return astring(DEFAULT_SEPARATOR, 1); }
94 astring &filename::raw() { return *this; }
96 const astring &filename::raw() const { return *this; }
98 bool filename::good() const { return exists(); }
100 bool filename::unlink() const { return ::unlink(observe()) == 0; }
102 void filename::reset(const astring &name) {
104 _had_directory = true; // until we know better.
108 astring filename::null_device()
117 bool filename::separator(char is_it)
118 { return (is_it == pc_separator) || (is_it == unix_separator); }
120 filename &filename::operator = (const filename &to_copy)
122 if (this == &to_copy) return *this;
123 (astring &)(*this) = to_copy;
124 _had_directory = to_copy._had_directory;
128 filename &filename::operator = (const astring &to_copy)
130 _had_directory = true;
131 if (this == &to_copy) return *this;
132 (astring &)(*this) = to_copy;
137 astring filename::pop()
139 astring to_return = basename();
140 filename parent_dir = parent();
141 if (parent_dir.raw().equal_to(NO_PARENT_DEFAULT)) {
142 // we haven't gone anywhere.
143 return ""; // signal that nothing was removed.
149 filename filename::parent() const { return dirname(); }
151 void filename::push(const astring &to_push)
153 *this = filename(*this, to_push);
156 void filename::canonicalize()
158 FUNCDEF("canonicalize");
159 // turn all the non-default separators into the default.
160 bool found_sep = false;
161 for (int j = 0; j < length(); j++) {
162 if (separator(get(j))) {
164 put(j, DEFAULT_SEPARATOR);
168 // if there wasn't a single directory separator, then they must not have
169 // specified any directory name for this filename (although it could itself
171 if (!found_sep) _had_directory = false;
173 // remove all occurrences of double separators except for the first
174 // double set, which could be a UNC filename. that's why the index below
175 // starts at one rather than zero.
176 bool saw_sep = false;
177 for (int i = 1; i < length(); i++) {
178 if (separator(get(i))) {
181 // two in a row is no good, except for the first two.
182 i--; // skip back one and try again.
186 } else saw_sep = false;
190 // on windows, we want to translate away from any cygwin or msys format into a more palatable
191 // version that the rest of windows understands.
193 //hmmm: make these into statics!
194 const astring CYGDRIVE_SENTINEL = "cygdrive";
195 const astring CYGDRIVE_PATH = astring(astring(DEFAULT_SEPARATOR, 1)
196 + CYGDRIVE_SENTINEL + astring(DEFAULT_SEPARATOR, 1));
198 // must be at least as long as the string we're looking for, plus a drive letter afterwards.
199 if ( (length() >= CYGDRIVE_PATH.length() + 1)
201 && separator(get(CYGDRIVE_PATH.length() - 1))
202 && compare(CYGDRIVE_SENTINEL, 1,
203 0, CYGDRIVE_SENTINEL.length(), true) ) {
204 zap(0, CYGDRIVE_PATH.length() - 1); // whack the cygdrive portion plus two slashes.
205 insert(1, ":"); // add a colon after the imputed drive letter.
206 //LOG(astring("turned cygdrive path string into: ") + *this);
208 //LOG(astring("path didn't match so left as: ") + *this);
210 // now we convert msys...
211 if ( (length() >= 2) && (get(0) == DEFAULT_SEPARATOR)
212 && textual::parser_bits::is_alpha(get(1)) ) {
213 // we seem reasonably sure now that this is a windows path hiding in msys format, but
214 // the next character needs to be a slash (if there is a next character) for it to be
215 // the windows drive form. otherwise it could be /tmp, which would obviously not be
216 // intended as a windows path.
217 if ( (length() == 2) || (get(2) == DEFAULT_SEPARATOR) ) {
218 // cool, this should be interpretable as an msys path, except for those wacky types
219 // of folks that might use a top-level single character directory name. we cannot
220 // help them, because we have made a design decision to support msys-style paths.
221 // note that this would only affect someone if they were referring to their directory on
222 // the current windows partition (c:, d:, etc.) without providing the drive letter,
223 // if they had that single character directory name (e.g., c:\x, d:\q, etc.) and even
224 // then only on the near defunct windows platform.
225 zap(0, 0); // take off initial slash.
226 insert(1, ":"); // add the obligatory colon.
227 //LOG(astring("turned msys string into: ") + *this);
231 // if no specialized path specifications were seen, and we have a unix style path
232 // here, then there will be trouble when we pass that to windows.
233 //if first character is a slash, and second char is alphanumeric, then we check...
234 //can we find a cygwin root dir stored in our config stuff?
235 // maybe in the build version file? ugh, yuck.
236 // what about in generated files, created at build time? --> yes, nice option.
238 //hmmm: we need the capability to re-create the config file that tells us
239 // where cyg root is, but how can we, aside from guessing at where to find
240 // cygwin (c:/cygwin c:/cygwin64 etc).
246 // we don't crop the last separator if the name's too small. for msdos
247 // names, that would be chopping a slash off the c:\ style name.
249 // zap any separators that are hiding on the end.
250 const int last = end();
251 if (separator(get(last))) zap(last, last);
252 } else if ( (length() == 2) && (get(1) == ':') ) {
253 // special case for dos drive names. we turn it back into a valid
254 // directory rather than leaving it as just "X:". that form of the name
255 // means something else under dos/windows.
256 *this += astring(DEFAULT_SEPARATOR, 1);
260 char filename::drive(bool interact_with_fs) const
262 // first guess: if second letter's a colon, first letter's the drive.
267 if (!interact_with_fs)
270 // otherwise, retrieve the file system's record for the file.
272 if (!get_info(&fill))
274 return char('A' + fill.st_dev);
277 astring filename::extension() const
279 astring base(basename().raw());
280 int posn = base.find('.', base.end(), true);
283 return base.substring(posn + 1, base.length() - 1);
286 astring filename::rootname() const
288 astring base(basename().raw());
289 int posn = base.find('.', base.end(), true);
292 return base.substring(0, posn - 1);
295 bool filename::get_info(status_info *to_fill) const
297 int ret = stat(observe(), to_fill);
303 bool filename::is_directory() const
306 if (!get_info(&fill))
308 return !!(fill.st_mode & S_IFDIR);
311 bool filename::is_writable() const
314 if (!get_info(&fill))
316 return !!(fill.st_mode & S_IWRITE);
319 bool filename::is_readable() const
322 if (!get_info(&fill))
324 return !!(fill.st_mode & S_IREAD);
327 bool filename::is_executable() const
330 if (!get_info(&fill))
332 return !!(fill.st_mode & S_IEXEC);
335 bool filename::is_normal() const
338 if (!get_info(&fill))
340 #if defined(__WIN32__) || defined(__VMS__)
341 //hmmm: is there a corresponding set of functions for windows, where applicable?
344 bool weird = S_ISCHR(fill.st_mode)
345 || S_ISBLK(fill.st_mode)
346 || S_ISFIFO(fill.st_mode)
347 || S_ISSOCK(fill.st_mode);
352 int filename::find_last_separator(const astring &look_at) const
357 sep = look_at.find(DEFAULT_SEPARATOR, last_sep + 1);
358 if (sep >= 0) last_sep = sep;
363 filename filename::basename() const
365 astring basename = *this;
366 int last_sep = find_last_separator(basename);
367 if (last_sep >= 0) basename.zap(0, last_sep);
371 filename filename::dirname() const
373 astring dirname = *this;
374 int last_sep = find_last_separator(dirname);
375 // we don't accept ripping off the first slash.
377 // we can rip the slash and suffix off to get the directory name. however,
378 // this might be in the form X: on windows. if they want the slash to
379 // remain, they can use the dirname that appends it.
380 dirname.zap(last_sep, dirname.end());
382 if (get(0) == DEFAULT_SEPARATOR) {
383 // handle when we're up at the top of the filesystem. on unix, once
384 // you hit the root, you can keep going up but you still remain at
385 // the root. similarly on windoze, if there's no drive name in there.
386 dirname = astring(DEFAULT_SEPARATOR, 1);
388 // there's no slash at all in the filename any more. we assume that
389 // the directory is the current one, if no other information is
390 // available. this default is already used by some code.
391 dirname = NO_PARENT_DEFAULT;
397 astring filename::dirname(bool add_slash) const
399 astring tempname = dirname().raw();
400 if (add_slash) tempname += DEFAULT_SEPARATOR;
404 bool filename::exists() const
408 // if the file name is empty, that cannot exist.
411 return is_readable();
414 bool filename::legal_character(char to_check)
419 case '*': case '?': case '$': case '&': case '|':
420 case '\'': case '"': case '`':
426 default: return true;
430 void filename::detooth_filename(astring &to_clean, char replacement)
432 for (int i = 0; i < to_clean.length(); i++) {
433 if (!legal_character(to_clean[i]))
434 to_clean[i] = replacement;
438 int filename::packed_size() const
440 return PACKED_SIZE_INT32 + astring::packed_size();
443 void filename::pack(byte_array &packed_form) const
445 attach(packed_form, int(_had_directory));
446 astring::pack(packed_form);
449 bool filename::unpack(byte_array &packed_form)
452 if (!detach(packed_form, temp))
454 _had_directory = temp;
455 if (!astring::unpack(packed_form))
460 void filename::separate(bool &rooted, string_array &pieces) const
463 const astring &raw_form = raw();
464 astring accumulator; // holds the names we find.
465 rooted = raw_form.length() && separator(raw_form[0]);
466 for (int i = 0; i < raw_form.length(); i++) {
467 if (separator(raw_form[i])) {
468 // this is a separator character, so eat it and add the accumulated
469 // string to the list.
470 if (i && accumulator.length()) pieces += accumulator;
471 // now reset our accumulated text.
472 accumulator = astring::empty_string();
474 // not a separator, so just accumulate it.
475 accumulator += raw_form[i];
478 if (accumulator.length()) pieces += accumulator;
481 void filename::join(bool rooted, const string_array &pieces)
483 astring constructed_name; // we'll make a filename here.
484 if (rooted) constructed_name += DEFAULT_SEPARATOR;
485 for (int i = 0; i < pieces.length(); i++) {
486 constructed_name += pieces[i];
487 if (!i || (i != pieces.length() - 1))
488 constructed_name += DEFAULT_SEPARATOR;
490 *this = constructed_name;
493 bool filename::base_compare_prefix(const filename &to_compare,
494 string_array &first, string_array &second)
497 separate(first_rooted, first);
499 to_compare.separate(second_rooted, second);
500 if (first_rooted != second_rooted) {
503 // that case should never be allowed, since there are some bits missing
504 // in the name to be compared.
505 if (first.length() > second.length())
508 // compare each of the pieces.
509 for (int i = 0; i < first.length(); i++) {
510 #if defined(__WIN32__) || defined(__VMS__)
511 // case-insensitive compare.
512 if (!first[i].iequals(second[i]))
515 // case-sensitive compare.
516 if (first[i] != second[i])
523 bool filename::compare_prefix(const filename &to_compare, astring &sequel)
525 sequel = astring::empty_string(); // clean our output parameter.
528 if (!base_compare_prefix(to_compare, first, second))
531 // create the sequel string.
532 int extra_strings = second.length() - first.length();
533 for (int i = second.length() - extra_strings; i < second.length(); i++) {
535 if (i != second.length() - 1) sequel += DEFAULT_SEPARATOR;
541 bool filename::compare_prefix(const filename &to_compare)
545 return base_compare_prefix(to_compare, first, second);
548 bool filename::base_compare_suffix(const filename &to_compare,
549 string_array &first, string_array &second)
552 separate(first_rooted, first);
554 to_compare.separate(second_rooted, second);
555 // that case should never be allowed, since there are some bits missing
556 // in the name to be compared.
557 if (first.length() > second.length())
560 // compare each of the pieces.
561 for (int i = first.length() - 1; i >= 0; i--) {
562 //clean up this computation; the difference in lengths is constant--use that.
563 int distance_from_end = first.length() - 1 - i;
564 int j = second.length() - 1 - distance_from_end;
565 #if defined(__WIN32__) || defined(__VMS__)
566 // case-insensitive compare.
567 if (!first[i].iequals(second[j]))
570 // case-sensitive compare.
571 if (first[i] != second[j])
578 bool filename::compare_suffix(const filename &to_compare, astring &prequel)
580 prequel = astring::empty_string(); // clean our output parameter.
583 if (!base_compare_suffix(to_compare, first, second))
586 // create the prequel string.
587 int extra_strings = second.length() - first.length();
588 for (int i = 0; i < extra_strings; i++) {
589 prequel += second[i];
590 if (i != second.length() - 1) prequel += DEFAULT_SEPARATOR;
595 bool filename::compare_suffix(const filename &to_compare)
599 return base_compare_suffix(to_compare, first, second);
602 bool filename::chmod(int write_mode, int owner_mode) const
606 if (write_mode & ALLOW_READ) {
607 if (owner_mode & USER_RIGHTS) chmod_value |= S_IRUSR;
608 if (owner_mode & GROUP_RIGHTS) chmod_value |= S_IRGRP;
609 if (owner_mode & OTHER_RIGHTS) chmod_value |= S_IROTH;
611 if (write_mode & ALLOW_WRITE) {
612 if (owner_mode & USER_RIGHTS) chmod_value |= S_IWUSR;
613 if (owner_mode & GROUP_RIGHTS) chmod_value |= S_IWGRP;
614 if (owner_mode & OTHER_RIGHTS) chmod_value |= S_IWOTH;
616 //// chmod_value = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
617 #elif defined(__WIN32__)
618 if (write_mode & ALLOW_READ) {
619 chmod_value |= _S_IREAD;
621 if (write_mode & ALLOW_WRITE) {
622 chmod_value |= _S_IWRITE;
625 #error unsupported OS type currently.
627 int chmod_result = ::chmod(raw().s(), chmod_value);
629 // LOG(astring("there was a problem changing permissions on ") + raw());