filename.cpp

Go to the documentation of this file.
00001 /*****************************************************************************\
00002 *                                                                             *
00003 *  Name   : filename                                                          *
00004 *  Author : Chris Koeritz                                                     *
00005 *                                                                             *
00006 *******************************************************************************
00007 * Copyright (c) 1993-$now By Author.  This program is free software; you can  *
00008 * redistribute it and/or modify it under the terms of the GNU General Public  *
00009 * License as published by the Free Software Foundation; either version 2 of   *
00010 * the License or (at your option) any later version.  This is online at:      *
00011 *     http://www.fsf.org/copyleft/gpl.html                                    *
00012 * Please send any updates to: fred@gruntose.com                               *
00013 \*****************************************************************************/
00014 
00015 // implementation note: the filename is kept canonicalized.  any constructor
00016 // or assignment operator should ensure this (except the blank constructor).
00017 
00018 #include "filename.h"
00019 
00020 #include <basis/byte_array.h>
00021 #include <basis/functions.h>
00022 
00023 #include <stdio.h>
00024 #include <sys/stat.h>
00025 #include <sys/types.h>
00026 #ifdef __UNIX__
00027   #include <unistd.h>
00028 #endif
00029 #ifdef __WIN32__
00030   #include <io.h>
00031 #endif
00032 
00033 using namespace basis;
00034 using namespace structures;
00035 
00036 class status_info : public stat
00037 {
00038 };
00039 
00040 namespace filesystem {
00041 
00042 #if defined(__WIN32__) || defined(__VMS__)
00043   const char DEFAULT_SEPARATOR = '\\';
00044 #elif defined(__UNIX__)
00045   const char DEFAULT_SEPARATOR = '/';
00046 #else
00047   #error "We have no idea what the default path separator is."
00048 #endif
00049 
00050 const char *NO_PARENT_DEFAULT = ".";
00051   // used when no directory name can be popped off.
00052 
00053 filename::filename()
00054 : astring(),
00055   _had_directory(false)
00056 {}
00057 
00058 filename::filename(const astring &name)
00059 : astring(name),
00060   _had_directory(true)
00061 { canonicalize(); }
00062 
00063 filename::filename(const astring &directory, const astring &name_of_file)
00064 : astring(directory),
00065   _had_directory(true)
00066 {
00067   // if the directory is empty, use the current directory.
00068   if (!directory) {
00069     *this = astring(NO_PARENT_DEFAULT);
00070     _had_directory = false;
00071   }
00072   // check for a slash on the end of the directory.  add one if there is none
00073   // currently.
00074   bool add_slash = false;
00075   if ( (directory[directory.end()] != '\\')
00076        && (directory[directory.end()] != '/') ) add_slash = true;
00077   if (add_slash) *this += DEFAULT_SEPARATOR;
00078   *this += name_of_file;
00079   canonicalize();
00080 }
00081 
00082 filename::filename(const filename &to_copy)
00083 : astring(to_copy),
00084   _had_directory(to_copy._had_directory)
00085 { canonicalize(); }
00086 
00087 filename::~filename() {}
00088 
00089 astring filename::default_separator() { return astring(DEFAULT_SEPARATOR, 1); }
00090 
00091 astring &filename::raw() { return *this; }
00092 
00093 const astring &filename::raw() const { return *this; }
00094 
00095 bool filename::good() const { return exists(); }
00096 
00097 bool filename::unlink() const { return ::unlink(observe()) == 0; }
00098 
00099 astring filename::null_device()
00100 {
00101 #ifdef __WIN32__
00102   return "null:";
00103 #else
00104   return "/dev/null";
00105 #endif
00106 }
00107 
00108 bool filename::separator(char is_it)
00109 { return (is_it == pc_separator) || (is_it == unix_separator); }
00110 
00111 filename &filename::operator = (const filename &to_copy)
00112 {
00113   if (this == &to_copy) return *this;
00114   (astring &)(*this) = to_copy;
00115   _had_directory = to_copy._had_directory;
00116   return *this;
00117 }
00118 
00119 filename &filename::operator = (const astring &to_copy)
00120 {
00121   _had_directory = true;
00122   if (this == &to_copy) return *this;
00123   (astring &)(*this) = to_copy;
00124   canonicalize();
00125   return *this;
00126 }
00127 
00128 astring filename::pop()
00129 {
00130   astring to_return = basename();
00131   filename parent_dir = parent();
00132   if (parent_dir.raw().equal_to(NO_PARENT_DEFAULT)) {
00133     // we haven't gone anywhere.
00134     return "";  // signal that nothing was removed.
00135   }
00136   *this = parent_dir;
00137   return to_return;
00138 }
00139 
00140 filename filename::parent() const { return dirname(); }
00141 
00142 void filename::push(const astring &to_push)
00143 {
00144   *this = filename(*this, to_push);
00145 }
00146 
00147 void filename::canonicalize()
00148 {
00149   // turn all the non-default separators into the default.
00150   bool found_sep = false;
00151   for (int j = 0; j < length(); j++) {
00152     if (separator(get(j))) {
00153       found_sep = true;
00154       put(j, DEFAULT_SEPARATOR);
00155     }
00156   }
00157 
00158   // if there wasn't a single directory separator, then they must not have
00159   // specified any directory name for this filename (although it could itself
00160   // be a directory).
00161   if (!found_sep) _had_directory = false;
00162 
00163   // remove all occurrences of double separators except for the first
00164   // double set, which could be a UNC filename.  that's why the index below
00165   // starts at one rather than zero.
00166   bool saw_sep = false;
00167   for (int i = 1; i < length(); i++) {
00168     if (separator(get(i))) {
00169       if (saw_sep) {
00170         zap(i, i);
00171           // two in a row is no good, except for the first two.
00172         i--;  // skip back one and try again.
00173         continue;
00174       }
00175       saw_sep = true;
00176     } else saw_sep = false;
00177   }
00178 
00179   // we don't crop the last separator if the name's too small.  for msdos
00180   // names, that would be chopping a slash off the c:\ style name.
00181   if (length() > 3) {
00182     // zap any separators that are hiding on the end.
00183     const int last = end();
00184     if (separator(get(last))) zap(last, last);
00185   } else if ( (length() == 2) && (get(1) == ':') ) {
00186     // special case for dos drive names.  we turn it back into a valid
00187     // directory rather than leaving it as just "X:".  that form of the name
00188     // means something else under dos/windows.
00189     *this += astring(DEFAULT_SEPARATOR, 1);
00190   }
00191 }
00192 
00193 char filename::drive(bool interact_with_fs) const
00194 {
00195   // first guess: if second letter's a colon, first letter's the drive.
00196   if (length() < 2)
00197     return '\0';
00198   if (get(1) == ':')
00199     return get(0);
00200   if (!interact_with_fs)
00201     return '\0';
00202 
00203   // otherwise, retrieve the file system's record for the file.
00204   status_info fill;
00205   if (!get_info(&fill))
00206     return '\0';
00207   return char('A' + fill.st_dev);
00208 }
00209 
00210 astring filename::extension() const
00211 {
00212   astring base(basename().raw());
00213   int posn = base.find('.', base.end(), true);
00214   if (negative(posn))
00215     return "";
00216   return base.substring(posn + 1, base.length() - 1);
00217 }
00218 
00219 astring filename::rootname() const
00220 {
00221   astring base(basename().raw());
00222   int posn = base.find('.', base.end(), true);
00223   if (negative(posn))
00224     return base;
00225   return base.substring(0, posn - 1);
00226 }
00227 
00228 bool filename::get_info(status_info *to_fill) const
00229 {
00230   int ret = stat(observe(), to_fill);
00231   if (ret)
00232     return false;
00233   return true;
00234 }
00235 
00236 bool filename::is_directory() const
00237 {
00238   status_info fill;
00239   if (!get_info(&fill))
00240     return false;
00241   return !!(fill.st_mode & S_IFDIR);
00242 }
00243 
00244 bool filename::is_writable() const
00245 {
00246   status_info fill;
00247   if (!get_info(&fill))
00248     return false;
00249   return !!(fill.st_mode & S_IWRITE);
00250 }
00251 
00252 bool filename::is_readable() const
00253 {
00254   status_info fill;
00255   if (!get_info(&fill))
00256     return false;
00257   return !!(fill.st_mode & S_IREAD);
00258 }
00259 
00260 bool filename::is_executable() const
00261 {
00262   status_info fill;
00263   if (!get_info(&fill))
00264     return false;
00265   return !!(fill.st_mode & S_IEXEC);
00266 }
00267 
00268 int filename::find_last_separator(const astring &look_at) const
00269 {
00270   int last_sep = -1;
00271   int sep = 0;
00272   while (sep >= 0) {
00273     sep = look_at.find(DEFAULT_SEPARATOR, last_sep + 1);
00274     if (sep >= 0) last_sep = sep;
00275   }
00276   return last_sep;
00277 }
00278 
00279 filename filename::basename() const
00280 {
00281   astring basename = *this;
00282   int last_sep = find_last_separator(basename);
00283   if (last_sep >= 0) basename.zap(0, last_sep);
00284   return basename;
00285 }
00286 
00287 filename filename::dirname() const
00288 {
00289   astring dirname = *this;
00290   int last_sep = find_last_separator(dirname);
00291   // we don't accept ripping off the first slash.
00292   if (last_sep >= 1) {
00293     // we can rip the slash and suffix off to get the directory name.  however,
00294     // this might be in the form X: on windows.  if they want the slash to
00295     // remain, they can use the dirname that appends it.
00296     dirname.zap(last_sep, dirname.end());
00297   } else {
00298     if (get(0) == DEFAULT_SEPARATOR) {
00299       // handle when we're up at the top of the filesystem.  on unix, once
00300       // you hit the root, you can keep going up but you still remain at
00301       // the root.  similarly on windoze, if there's no drive name in there.
00302       dirname = astring(DEFAULT_SEPARATOR, 1);
00303     } else {
00304       // there's no slash at all in the filename any more.  we assume that
00305       // the directory is the current one, if no other information is
00306       // available.  this default is already used by some code.
00307       dirname = NO_PARENT_DEFAULT;
00308     }
00309   }
00310   return dirname;
00311 }
00312 
00313 astring filename::dirname(bool add_slash) const
00314 {
00315   astring tempname = dirname().raw();
00316   if (add_slash) tempname += DEFAULT_SEPARATOR;
00317   return tempname;
00318 }
00319 
00320 bool filename::exists() const
00321 {
00322   if (is_directory())
00323     return true;
00324   if (!length())
00325     return false;
00326   return is_readable();
00329 }
00330 
00331 bool filename::legal_character(char to_check)
00332 {
00333   switch (to_check) {
00334     case ':': case ';':
00335     case '\\': case '/':
00336     case '*': case '?': case '$': case '&': case '|':
00337     case '\'': case '"': case '`':
00338     case '(': case ')':
00339     case '[': case ']':
00340     case '<': case '>':
00341     case '{': case '}':
00342       return false;
00343     default: return true;
00344   }
00345 }
00346 
00347 void filename::detooth_filename(astring &to_clean, char replacement)
00348 {
00349   for (int i = 0; i < to_clean.length(); i++) {
00350     if (!legal_character(to_clean[i]))
00351       to_clean[i] = replacement;
00352   }
00353 }
00354 
00355 int filename::packed_size() const
00356 {
00357   return PACKED_SIZE_INT32 + astring::packed_size();
00358 }
00359 
00360 void filename::pack(byte_array &packed_form) const
00361 {
00362   attach(packed_form, int(_had_directory));
00363   astring::pack(packed_form);
00364 }
00365 
00366 bool filename::unpack(byte_array &packed_form)
00367 {
00368   int temp;
00369   if (!detach(packed_form, temp))
00370     return false;
00371   _had_directory = temp;
00372   if (!astring::unpack(packed_form))
00373     return false;
00374   return true;
00375 }
00376 
00377 void filename::separate(string_array &pieces) const
00378 {
00379   pieces.reset();
00380   const astring &raw_form = raw();
00381   astring accumulator;  // holds the names we find.
00382   for (int i = 0; i < raw_form.length(); i++) {
00383     if (separator(raw_form[i])) {
00384       // this is a separator character, so eat it and add the accumulated
00385       // string to the list.
00386       if (!i || accumulator.length()) pieces += accumulator;
00387       // now reset our accumulated text.
00388       accumulator = astring::empty_string();
00389     } else {
00390       // not a separator, so just accumulate it.
00391       accumulator += raw_form[i];
00392     }
00393   }
00394   if (accumulator.length()) pieces += accumulator;
00395 }
00396 
00397 void filename::join(const string_array &pieces)
00398 {
00399   astring constructed_name;  // we'll make a filename here.
00400   for (int i = 0; i < pieces.length(); i++) {
00401     constructed_name += pieces[i];
00402     if (!i || (i != pieces.length() - 1))
00403       constructed_name += DEFAULT_SEPARATOR;
00404   }
00405   *this = constructed_name;
00406 }
00407 
00408 bool filename::base_compare_prefix(const filename &to_compare,
00409     string_array &first, string_array &second)
00410 {
00411   separate(first);
00412   to_compare.separate(second);
00413   // that case should never be allowed, since there are some bits missing
00414   // in the name to be compared.
00415   if (first.length() > second.length())
00416     return false;
00417 
00418   // compare each of the pieces.
00419   for (int i = 0; i < first.length(); i++) {
00420 #if defined(__WIN32__) || defined(__VMS__)
00421     // case-insensitive compare.
00422     if (!first[i].iequals(second[i]))
00423       return false;
00424 #else
00425     // case-sensitive compare.
00426     if (first[i] != second[i])
00427       return false;
00428 #endif
00429   }
00430   return true;
00431 }
00432 
00433 bool filename::compare_prefix(const filename &to_compare, astring &sequel)
00434 {
00435   sequel = astring::empty_string();  // clean our output parameter.
00436   string_array first;
00437   string_array second;
00438   if (!base_compare_prefix(to_compare, first, second))
00439     return false;
00440 
00441   // create the sequel string.
00442   int extra_strings = second.length() - first.length();
00443   for (int i = second.length() - extra_strings; i < second.length(); i++) {
00444     sequel += second[i];
00445     if (i != second.length() - 1) sequel += DEFAULT_SEPARATOR;
00446   }
00447 
00448   return true;
00449 }
00450 
00451 bool filename::compare_prefix(const filename &to_compare)
00452 {
00453   string_array first;
00454   string_array second;
00455   return base_compare_prefix(to_compare, first, second);
00456 }
00457 
00458 bool filename::base_compare_suffix(const filename &to_compare,
00459     string_array &first, string_array &second)
00460 {
00461   separate(first);
00462   to_compare.separate(second);
00463   // that case should never be allowed, since there are some bits missing
00464   // in the name to be compared.
00465   if (first.length() > second.length())
00466     return false;
00467 
00468   // compare each of the pieces.
00469   for (int i = first.length() - 1; i >= 0; i--) {
00470 //clean up this computation; the difference in lengths is constant--use that.
00471     int distance_from_end = first.length() - 1 - i;
00472     int j = second.length() - 1 - distance_from_end;
00473 #if defined(__WIN32__) || defined(__VMS__)
00474     // case-insensitive compare.
00475     if (!first[i].iequals(second[j]))
00476       return false;
00477 #else
00478     // case-sensitive compare.
00479     if (first[i] != second[j])
00480       return false;
00481 #endif
00482   }
00483   return true;
00484 }
00485 
00486 bool filename::compare_suffix(const filename &to_compare, astring &prequel)
00487 {
00488   prequel = astring::empty_string();  // clean our output parameter.
00489   string_array first;
00490   string_array second;
00491   if (!base_compare_suffix(to_compare, first, second))
00492     return false;
00493 
00494   // create the prequel string.
00495   int extra_strings = second.length() - first.length();
00496   for (int i = 0; i < extra_strings; i++) {
00497     prequel += second[i];
00498     if (i != second.length() - 1) prequel += DEFAULT_SEPARATOR;
00499   }
00500   return true;
00501 }
00502 
00503 bool filename::compare_suffix(const filename &to_compare)
00504 {
00505   string_array first;
00506   string_array second;
00507   return base_compare_suffix(to_compare, first, second);
00508 }
00509 
00510 bool filename::chmod(int write_mode, int owner_mode) const
00511 {
00512   int chmod_value = 0;
00513 #ifdef __UNIX__
00514   if (write_mode & ALLOW_READ) {
00515     if (owner_mode & USER_RIGHTS) chmod_value |= S_IRUSR;
00516     if (owner_mode & GROUP_RIGHTS) chmod_value |= S_IRGRP;
00517     if (owner_mode & OTHER_RIGHTS) chmod_value |= S_IROTH;
00518   }
00519   if (write_mode & ALLOW_WRITE) {
00520     if (owner_mode & USER_RIGHTS) chmod_value |= S_IWUSR;
00521     if (owner_mode & GROUP_RIGHTS) chmod_value |= S_IWGRP;
00522     if (owner_mode & OTHER_RIGHTS) chmod_value |= S_IWOTH;
00523   }
00525 #elif defined(__WIN32__)
00526   if (write_mode & ALLOW_READ) {
00527     chmod_value |= _S_IREAD;
00528   }
00529   if (write_mode & ALLOW_WRITE) {
00530     chmod_value |= _S_IWRITE;
00531   }
00532 #else
00533   #error unsupported OS type currently.
00534 #endif
00535   int chmod_result = ::chmod(raw().s(), chmod_value);
00536   if (chmod_result) {
00537 //    LOG(astring("there was a problem changing permissions on ") + raw());
00538     return false;
00539   }
00540   return true;
00541 }
00542 
00543 } //namespace.
00544 
Generated on Sat Jan 28 04:22:22 2012 for hoople2 project by  doxygen 1.6.3