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>
25 #include <sys/types.h>
33 using namespace basis;
34 using namespace structures;
36 class status_info : public stat
40 namespace filesystem {
42 #if defined(__WIN32__) || defined(__VMS__)
43 const char DEFAULT_SEPARATOR = '\\';
44 #elif defined(__UNIX__)
45 const char DEFAULT_SEPARATOR = '/';
47 #error "We have no idea what the default path separator is."
50 const char *NO_PARENT_DEFAULT = ".";
51 // used when no directory name can be popped off.
58 filename::filename(const astring &name)
63 filename::filename(const astring &directory, const astring &name_of_file)
67 // if the directory is empty, use the current directory.
69 *this = astring(NO_PARENT_DEFAULT);
70 _had_directory = false;
72 // check for a slash on the end of the directory. add one if there is none
74 bool add_slash = false;
75 if ( (directory[directory.end()] != '\\')
76 && (directory[directory.end()] != '/') ) add_slash = true;
77 if (add_slash) *this += DEFAULT_SEPARATOR;
78 *this += name_of_file;
82 filename::filename(const filename &to_copy)
84 _had_directory(to_copy._had_directory)
87 filename::~filename() {}
89 astring filename::default_separator() { return astring(DEFAULT_SEPARATOR, 1); }
91 astring &filename::raw() { return *this; }
93 const astring &filename::raw() const { return *this; }
95 bool filename::good() const { return exists(); }
97 bool filename::unlink() const { return ::unlink(observe()) == 0; }
99 astring filename::null_device()
108 bool filename::separator(char is_it)
109 { return (is_it == pc_separator) || (is_it == unix_separator); }
111 filename &filename::operator = (const filename &to_copy)
113 if (this == &to_copy) return *this;
114 (astring &)(*this) = to_copy;
115 _had_directory = to_copy._had_directory;
119 filename &filename::operator = (const astring &to_copy)
121 _had_directory = true;
122 if (this == &to_copy) return *this;
123 (astring &)(*this) = to_copy;
128 astring filename::pop()
130 astring to_return = basename();
131 filename parent_dir = parent();
132 if (parent_dir.raw().equal_to(NO_PARENT_DEFAULT)) {
133 // we haven't gone anywhere.
134 return ""; // signal that nothing was removed.
140 filename filename::parent() const { return dirname(); }
142 void filename::push(const astring &to_push)
144 *this = filename(*this, to_push);
147 void filename::canonicalize()
149 // turn all the non-default separators into the default.
150 bool found_sep = false;
151 for (int j = 0; j < length(); j++) {
152 if (separator(get(j))) {
154 put(j, DEFAULT_SEPARATOR);
158 // if there wasn't a single directory separator, then they must not have
159 // specified any directory name for this filename (although it could itself
161 if (!found_sep) _had_directory = false;
163 // remove all occurrences of double separators except for the first
164 // double set, which could be a UNC filename. that's why the index below
165 // starts at one rather than zero.
166 bool saw_sep = false;
167 for (int i = 1; i < length(); i++) {
168 if (separator(get(i))) {
171 // two in a row is no good, except for the first two.
172 i--; // skip back one and try again.
176 } else saw_sep = false;
179 // we don't crop the last separator if the name's too small. for msdos
180 // names, that would be chopping a slash off the c:\ style name.
182 // zap any separators that are hiding on the end.
183 const int last = end();
184 if (separator(get(last))) zap(last, last);
185 } else if ( (length() == 2) && (get(1) == ':') ) {
186 // special case for dos drive names. we turn it back into a valid
187 // directory rather than leaving it as just "X:". that form of the name
188 // means something else under dos/windows.
189 *this += astring(DEFAULT_SEPARATOR, 1);
193 char filename::drive(bool interact_with_fs) const
195 // first guess: if second letter's a colon, first letter's the drive.
200 if (!interact_with_fs)
203 // otherwise, retrieve the file system's record for the file.
205 if (!get_info(&fill))
207 return char('A' + fill.st_dev);
210 astring filename::extension() const
212 astring base(basename().raw());
213 int posn = base.find('.', base.end(), true);
216 return base.substring(posn + 1, base.length() - 1);
219 astring filename::rootname() const
221 astring base(basename().raw());
222 int posn = base.find('.', base.end(), true);
225 return base.substring(0, posn - 1);
228 bool filename::get_info(status_info *to_fill) const
230 int ret = stat(observe(), to_fill);
236 bool filename::is_directory() const
239 if (!get_info(&fill))
241 return !!(fill.st_mode & S_IFDIR);
244 bool filename::is_writable() const
247 if (!get_info(&fill))
249 return !!(fill.st_mode & S_IWRITE);
252 bool filename::is_readable() const
255 if (!get_info(&fill))
257 return !!(fill.st_mode & S_IREAD);
260 bool filename::is_executable() const
263 if (!get_info(&fill))
265 return !!(fill.st_mode & S_IEXEC);
268 int filename::find_last_separator(const astring &look_at) const
273 sep = look_at.find(DEFAULT_SEPARATOR, last_sep + 1);
274 if (sep >= 0) last_sep = sep;
279 filename filename::basename() const
281 astring basename = *this;
282 int last_sep = find_last_separator(basename);
283 if (last_sep >= 0) basename.zap(0, last_sep);
287 filename filename::dirname() const
289 astring dirname = *this;
290 int last_sep = find_last_separator(dirname);
291 // we don't accept ripping off the first slash.
293 // we can rip the slash and suffix off to get the directory name. however,
294 // this might be in the form X: on windows. if they want the slash to
295 // remain, they can use the dirname that appends it.
296 dirname.zap(last_sep, dirname.end());
298 if (get(0) == DEFAULT_SEPARATOR) {
299 // handle when we're up at the top of the filesystem. on unix, once
300 // you hit the root, you can keep going up but you still remain at
301 // the root. similarly on windoze, if there's no drive name in there.
302 dirname = astring(DEFAULT_SEPARATOR, 1);
304 // there's no slash at all in the filename any more. we assume that
305 // the directory is the current one, if no other information is
306 // available. this default is already used by some code.
307 dirname = NO_PARENT_DEFAULT;
313 astring filename::dirname(bool add_slash) const
315 astring tempname = dirname().raw();
316 if (add_slash) tempname += DEFAULT_SEPARATOR;
320 bool filename::exists() const
326 return is_readable();
327 /// byte_filer opened(observe(), "rb");
328 /// return opened.good();
331 bool filename::legal_character(char to_check)
336 case '*': case '?': case '$': case '&': case '|':
337 case '\'': case '"': case '`':
343 default: return true;
347 void filename::detooth_filename(astring &to_clean, char replacement)
349 for (int i = 0; i < to_clean.length(); i++) {
350 if (!legal_character(to_clean[i]))
351 to_clean[i] = replacement;
355 int filename::packed_size() const
357 return PACKED_SIZE_INT32 + astring::packed_size();
360 void filename::pack(byte_array &packed_form) const
362 attach(packed_form, int(_had_directory));
363 astring::pack(packed_form);
366 bool filename::unpack(byte_array &packed_form)
369 if (!detach(packed_form, temp))
371 _had_directory = temp;
372 if (!astring::unpack(packed_form))
377 void filename::separate(string_array &pieces) const
380 const astring &raw_form = raw();
381 astring accumulator; // holds the names we find.
382 for (int i = 0; i < raw_form.length(); i++) {
383 if (separator(raw_form[i])) {
384 // this is a separator character, so eat it and add the accumulated
385 // string to the list.
386 if (!i || accumulator.length()) pieces += accumulator;
387 // now reset our accumulated text.
388 accumulator = astring::empty_string();
390 // not a separator, so just accumulate it.
391 accumulator += raw_form[i];
394 if (accumulator.length()) pieces += accumulator;
397 void filename::join(const string_array &pieces)
399 astring constructed_name; // we'll make a filename here.
400 for (int i = 0; i < pieces.length(); i++) {
401 constructed_name += pieces[i];
402 if (!i || (i != pieces.length() - 1))
403 constructed_name += DEFAULT_SEPARATOR;
405 *this = constructed_name;
408 bool filename::base_compare_prefix(const filename &to_compare,
409 string_array &first, string_array &second)
412 to_compare.separate(second);
413 // that case should never be allowed, since there are some bits missing
414 // in the name to be compared.
415 if (first.length() > second.length())
418 // compare each of the pieces.
419 for (int i = 0; i < first.length(); i++) {
420 #if defined(__WIN32__) || defined(__VMS__)
421 // case-insensitive compare.
422 if (!first[i].iequals(second[i]))
425 // case-sensitive compare.
426 if (first[i] != second[i])
433 bool filename::compare_prefix(const filename &to_compare, astring &sequel)
435 sequel = astring::empty_string(); // clean our output parameter.
438 if (!base_compare_prefix(to_compare, first, second))
441 // create the sequel string.
442 int extra_strings = second.length() - first.length();
443 for (int i = second.length() - extra_strings; i < second.length(); i++) {
445 if (i != second.length() - 1) sequel += DEFAULT_SEPARATOR;
451 bool filename::compare_prefix(const filename &to_compare)
455 return base_compare_prefix(to_compare, first, second);
458 bool filename::base_compare_suffix(const filename &to_compare,
459 string_array &first, string_array &second)
462 to_compare.separate(second);
463 // that case should never be allowed, since there are some bits missing
464 // in the name to be compared.
465 if (first.length() > second.length())
468 // compare each of the pieces.
469 for (int i = first.length() - 1; i >= 0; i--) {
470 //clean up this computation; the difference in lengths is constant--use that.
471 int distance_from_end = first.length() - 1 - i;
472 int j = second.length() - 1 - distance_from_end;
473 #if defined(__WIN32__) || defined(__VMS__)
474 // case-insensitive compare.
475 if (!first[i].iequals(second[j]))
478 // case-sensitive compare.
479 if (first[i] != second[j])
486 bool filename::compare_suffix(const filename &to_compare, astring &prequel)
488 prequel = astring::empty_string(); // clean our output parameter.
491 if (!base_compare_suffix(to_compare, first, second))
494 // create the prequel string.
495 int extra_strings = second.length() - first.length();
496 for (int i = 0; i < extra_strings; i++) {
497 prequel += second[i];
498 if (i != second.length() - 1) prequel += DEFAULT_SEPARATOR;
503 bool filename::compare_suffix(const filename &to_compare)
507 return base_compare_suffix(to_compare, first, second);
510 bool filename::chmod(int write_mode, int owner_mode) const
514 if (write_mode & ALLOW_READ) {
515 if (owner_mode & USER_RIGHTS) chmod_value |= S_IRUSR;
516 if (owner_mode & GROUP_RIGHTS) chmod_value |= S_IRGRP;
517 if (owner_mode & OTHER_RIGHTS) chmod_value |= S_IROTH;
519 if (write_mode & ALLOW_WRITE) {
520 if (owner_mode & USER_RIGHTS) chmod_value |= S_IWUSR;
521 if (owner_mode & GROUP_RIGHTS) chmod_value |= S_IWGRP;
522 if (owner_mode & OTHER_RIGHTS) chmod_value |= S_IWOTH;
524 //// chmod_value = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
525 #elif defined(__WIN32__)
526 if (write_mode & ALLOW_READ) {
527 chmod_value |= _S_IREAD;
529 if (write_mode & ALLOW_WRITE) {
530 chmod_value |= _S_IWRITE;
533 #error unsupported OS type currently.
535 int chmod_result = ::chmod(raw().s(), chmod_value);
537 // LOG(astring("there was a problem changing permissions on ") + raw());