root/trunk/whisperlib/common/io/ioutil.cc

Revision 7, 14.9 kB (checked in by whispercastorg, 2 years ago)

version 0.2.0

Line 
1 // Copyright (c) 2009, Whispersoft s.r.l.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 // * Neither the name of Whispersoft s.r.l. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 //
30 // Author: Catalin Popescu
31
32 #include <sys/types.h>
33 #include <sys/stat.h>
34
35 #include <dirent.h>
36 #include <limits.h>
37 #include <stddef.h>
38 #include <unistd.h>
39
40 #include "common/io/ioutil.h"
41 #include "common/base/strutil.h"
42 #include "common/base/log.h"
43 #include "common/base/errno.h"
44
45 namespace io {
46
47 bool IsDir(const char* name) {
48   struct stat64 st;
49   if ( 0 != stat64(name, &st) ) {
50     return false;
51   }
52   return S_ISDIR(st.st_mode);
53 }
54 bool IsDir(const string& name) {
55   return IsDir(name.c_str());
56 }
57
58 bool IsReadableFile(const char* name) {
59   struct stat64 st;
60   if ( 0 != stat64(name, &st) ) {
61     return false;
62   }
63   return S_ISREG(st.st_mode);
64 }
65 bool IsReadableFile(const string& name) {
66   return IsReadableFile(name.c_str());
67 }
68 bool IsSymlink(const string& path) {
69   struct stat64 st;
70   // NOTE: ::stat queries the file referenced by link, not the link itself.
71   if ( 0 != lstat64(path.c_str(), &st) ) {
72     return false;
73   }
74   return S_ISLNK(st.st_mode);
75 }
76 bool Exists(const char* path) {
77   struct stat64 st;
78   if ( 0 != lstat64(path, &st) ) {
79     return false;
80   }
81   return true;
82 }
83 bool Exists(const string& path) {
84   return Exists(path.c_str());
85 }
86 int64 GetFileSize(const char* name) {
87   struct stat64 st;
88   if ( 0 != stat64(name, &st) ) {
89     return -1;
90   }
91   return st.st_size;
92 }
93 int64 GetFileSize(const string& name) {
94   return GetFileSize(name.c_str());
95 }
96
97 // Per:  http://womble.decadentplace.org.uk/readdir_r-advisory.html
98 // * Calculate the required buffer size (in bytes) for directory       *
99 // * entries read from the given directory handle.  Return -1 if this  *
100 // * this cannot be done.                                              *
101 // *                                                                   *
102 // * This code does not trust values of NAME_MAX that are less than    *
103 // * 255, since some systems (including at least HP-UX) incorrectly    *
104 // * define it to be a smaller value.                                  *
105 // *                                                                   *
106 // * If you use autoconf, include fpathconf and dirfd in your          *
107 // * AC_CHECK_FUNCS list.  Otherwise use some other method to detect   *
108 // * and use them where available.                                     *
109 size_t dirent_buf_size(DIR* dirp) {
110     long name_max;
111     size_t name_end;
112 #   if defined(HAVE_FPATHCONF) && defined(HAVE_DIRFD) \
113        && defined(_PC_NAME_MAX)
114         name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX);
115         if (name_max == -1)
116 #           if defined(NAME_MAX)
117                 name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
118 #           else
119                 return (size_t)(-1);
120 #           endif
121 #   else
122 #       if defined(NAME_MAX)
123             name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
124 #       else
125 #           error "buffer size for readdir_r cannot be determined"
126 #       endif
127 #   endif
128     name_end = (size_t)offsetof(struct dirent, d_name) + name_max + 1;
129     return (name_end > sizeof(struct dirent)
130             ? name_end : sizeof(struct dirent));
131 }
132 bool DirList(const string& to_list,
133              vector<string>* names,
134              bool list_dirs,
135              re::RE* regex) {
136   DIR* dirp = opendir(to_list.c_str());
137   if ( NULL == dirp ) {
138     LOG_ERROR << "::opendir failed for dir: [" << to_list << "] error: "
139               << GetLastSystemErrorDescription();
140     return false;
141   }
142   size_t size = dirent_buf_size(dirp);
143   if ( size == -1 ) {
144     closedir(dirp);
145     LOG_ERROR << "error dirent_buf_size for dir: [" << to_list << "]";
146     return false;
147   }
148   struct dirent* entry;
149   struct dirent* buf = reinterpret_cast<struct dirent *>(malloc(size));
150   int error;
151   while ((error = readdir_r(dirp, buf, &entry)) == 0 && entry != NULL) {
152     struct stat64 st;
153     // Skip dots
154     if ( '.' == entry->d_name[0] &&
155          ('\0' == entry->d_name[1] ||
156           ('.' == entry->d_name[1] && '\0' == entry->d_name[2])) ) {
157       continue;
158     }
159     if ( 0 == lstat64(strutil::JoinPaths(to_list,
160                                          string(entry->d_name)).c_str(),
161                    &st) ) {
162       if ( S_ISDIR(st.st_mode) ) {
163         if ( list_dirs && (regex == NULL || regex->Matches(entry->d_name)) ) {
164           names->push_back(entry->d_name);
165         }
166       } else if ( regex == NULL || regex->Matches(entry->d_name) ) {
167         names->push_back(entry->d_name);
168       }
169     }
170   }
171   free(buf);
172   if ( error ) {
173     closedir(dirp);
174     LOG_ERROR << " error in readdir_r for dir: [" << to_list << "] error: "
175               << GetLastSystemErrorDescription();
176     return false;
177   }
178   closedir(dirp);
179   return true;
180 }
181
182
183 bool RecursiveListing(const string& dir,
184                       vector<string>* dir_names,
185                       re::RE* regex) {
186   vector<string> local_list;
187   if ( !DirList(dir, &local_list, true) ) {
188     return false;
189   }
190   vector<string> dirs;
191   for ( size_t i = 0; i < local_list.size(); ++i ) {
192     const string crt(strutil::JoinPaths(dir, local_list[i]));
193     if ( (regex == NULL || regex->Matches(local_list[i]) ) ) {
194       if ( io::IsReadableFile(crt.c_str()) ) {
195         dir_names->push_back(crt);
196         continue;
197       }
198     }
199     if ( IsDir(crt) ) {
200       dirs.push_back(crt);
201     }
202   }
203   for ( size_t i = 0; i < dirs.size(); ++i ) {
204     if ( !RecursiveListing(dirs[i], dir_names, regex) ) {
205       return false;
206     }
207   }
208   return true;
209 }
210
211 bool CreateRecursiveDirs(const char* dirname, mode_t mode) {
212   string crt_dir(strutil::NormalizePath(dirname));
213   if ( crt_dir.empty() ) return true;
214   if ( crt_dir[crt_dir.size() - 1] == '/' ) {
215     crt_dir.resize(crt_dir.size() - 1);   // cut any trailing '/'
216   }
217   vector<string> to_create;
218   while ( !crt_dir.empty() ) {
219     if ( io::IsDir(crt_dir.c_str()) ) {
220       break;
221     }
222     if ( io::IsReadableFile(crt_dir.c_str()) ) {
223       return false;
224     }
225     to_create.push_back(crt_dir);
226     const size_t pos_slash = crt_dir.find_last_of('/');
227     if ( pos_slash == string::npos ) {
228       break;
229     }
230     crt_dir = crt_dir.substr(0, pos_slash);
231   }
232   for ( int i = to_create.size() - 1; i >= 0; --i ) {
233     if ( ::mkdir(to_create[i].c_str(), mode) ) {
234       LOG_ERROR << "Error creating directory: " << to_create[i]
235                 << " : " << GetSystemErrorDescription(errno);
236       return false;
237     }
238   }
239   return true;
240 }
241 bool CreateRecursiveDirs(const string& dirname, mode_t mode) {
242   return CreateRecursiveDirs(dirname.c_str(), mode);
243 }
244
245
246 int32 GetLastNumberedFile(const string& dir,
247                           re::RE* re,
248                           int32 file_num_size) {
249   vector<string> files;
250   if ( !DirList(dir + "/", &files, false, re) ) {
251     return -2;
252   }
253   if ( files.empty() ) {
254     return -1;
255   }
256   sort(files.begin(), files.end());
257   CHECK_GT(files.back().size(), 10);
258   errno = 0;  // essential as strtol would not set a 0 errno
259   const int32 file_num = strtol(
260     files.back().c_str() + files.back().size() - file_num_size, NULL, 10);
261   if ( errno || file_num < 0 ) {
262     LOG_ERROR << " Invalid file found: " << files.back()
263               << " under: " << dir;
264     return -2;
265   }
266   return file_num;
267 }
268
269 bool Rm(const string& path) {
270   struct stat64 s;
271   // NOTE: if path is a symbolic link:
272   //       - lstat() doesn't follow symbolic links. It returns stats for
273   //                 the link itself.
274   //       - stat() follows symbolic links and returns stats for the linked file.
275   if ( ::lstat64(path.c_str(), &s) ) {
276     if ( GetLastSystemError() == ENOENT ) {
277       return true;
278     }
279     LOG_ERROR << "lstat failed for path: " << path
280               << " error: " << GetLastSystemErrorDescription();
281     return false;
282   }
283   if ( S_ISREG(s.st_mode) || S_ISLNK(s.st_mode) ) {
284     LOG_INFO << "Removing " << (S_ISREG(s.st_mode) ? "file" : "symlink")
285              << ": [" << path << "]";
286     if ( ::unlink(path.c_str()) ) {
287       LOG_ERROR << "unlink failed for path: " << path
288                 << " error: " << GetLastSystemErrorDescription();
289       return false;
290     }
291     return true;
292   }
293   if ( S_ISDIR(s.st_mode) ) {
294     struct dirent** namelist;
295     int n = ::scandir(path.c_str(), &namelist, 0, &alphasort);
296     if ( n < 0 ) {
297       LOG_ERROR << "scandir failed for path: ["
298                 << path << "] error: " << GetLastSystemErrorDescription();
299       return false;
300     }
301     for ( int i = 0; i < n; i++ ) {
302       if ( (namelist[i]->d_name[0] == '.' && namelist[i]->d_name[1] == 0) ||
303            (namelist[i]->d_name[0] == '.' &&
304             namelist[i]->d_name[1] == '.' &&
305             namelist[i]->d_name[2] == 0) ) {
306         continue;
307       }
308       Rm(path + "/" + namelist[i]->d_name);
309       ::free(namelist[i]);
310     }
311     LOG_INFO << "Removing dir: [" << path << "]";
312     ::rmdir(path.c_str());
313     ::free(namelist);
314     return true;
315   }
316   LOG_ERROR << "cannot remove file: [" << path
317             << "] unhandled mode: " << s.st_mode;
318   return false;
319 }
320 bool Rmdir(const string& path) {
321   int result = ::rmdir(path.c_str());
322   if ( result != 0 ) {
323     LOG_ERROR << "::rmdir failed for path: [" << path << "]"
324                  " error: " << GetLastSystemErrorDescription();
325     return false;
326   }
327   return true;
328 }
329
330 bool Mv(const string& path, const string& dir, bool overwrite) {
331   return Rename(path,
332                 strutil::JoinPaths(dir, strutil::Basename(path)),
333                 overwrite);
334 }
335
336 bool Rename(const string& old_path,
337             const string& new_path,
338             bool overwrite) {
339   const bool old_exists = io::Exists(old_path);
340   const bool old_is_file = io::IsReadableFile(old_path);
341   const bool old_is_dir = io::IsDir(old_path);
342   const bool old_is_symlink = io::IsSymlink(old_path);
343   const bool old_is_single = old_is_file || old_is_symlink;
344   const string old_type = (old_is_symlink ? "symlink" :
345                            old_is_file ? "file" :
346                            old_is_dir ? "directory" :
347                            "unknown");
348
349   const bool new_exists = io::Exists(new_path);
350   const bool new_is_file = io::IsReadableFile(new_path);
351   const bool new_is_dir = io::IsDir(new_path);
352   const bool new_is_symlink = io::IsSymlink(new_path);
353   const bool new_is_single = new_is_file || new_is_symlink;
354   const string new_type = (new_is_symlink ? "symlink" :
355                            new_is_file ? "file" :
356                            new_is_dir ? "directory" :
357                            "unknown");
358
359
360   if ( !old_exists ) {
361     LOG_ERROR << "Rename old_path: [" << old_path << "] does not exist";
362     return false;
363   }
364   if ( (old_is_single && new_is_dir) ||
365        (old_is_dir && new_is_single) ) {
366     LOG_ERROR << "Rename old_path: [" << old_path << "](" << old_type
367               << "), new_path: [" << new_path << "](" << new_type
368               << ") incompatible types";
369     return false;
370   }
371   if ( new_exists && new_is_single && !overwrite ) {
372     LOG_ERROR << "Rename old_path: [" << old_path << "](" << old_type
373               << ") , new_path: [" << new_path << "](" << new_type
374               << ") cannot overwrite";
375     return false;
376   }
377
378   // - move file or directory over empty_path
379   // - or move file over file
380   //
381   //  ! Atomically !
382   //
383   if ( !new_exists || (old_is_single && new_is_single) ) {
384     LOG_INFO << "Renaming [" << old_path << "] to [" << new_path << "]";
385     if ( ::rename(old_path.c_str(), new_path.c_str()) ) {
386       LOG_ERROR << "::rename failed, old_path: [" << old_path << "]"
387                 << ", new_path: [" << new_path << "]"
388                 << ", error: " << GetLastSystemErrorDescription();
389       return false;
390     }
391     return true;
392   }
393
394   // move directory over existing directory; we have to integrate
395   // the content of old directory into the existing path
396   //
397   // !  NOT Atomically  !
398
399   CHECK(old_exists);
400   CHECK(old_is_dir);
401   CHECK(new_exists);
402   CHECK(new_is_dir);
403
404   vector<string> old_entries;
405   if ( !io::DirList(old_path, &old_entries, true, NULL) ) {
406     LOG_ERROR << "io::DirList failed for old_path: [" << old_path
407               << "] error: " << GetLastSystemErrorDescription();
408     return false;
409   }
410   bool rename_all_success = true;
411   for ( vector<string>::const_iterator it = old_entries.begin();
412         it != old_entries.end(); ++it ) {
413     const string& old_name = *it;
414     const string old_entry = strutil::JoinPaths(old_path, old_name);
415     const string new_entry = strutil::JoinPaths(new_path, old_name);
416     bool success = Rename(old_entry, new_entry, overwrite);
417     rename_all_success = rename_all_success && success;
418   }
419   if ( rename_all_success ) {
420     Rm(old_path);
421   }
422   return rename_all_success;
423 }
424
425 bool Mkdir(const string& str_dir, bool recursive, mode_t mode) {
426   if ( recursive ) {
427     return io::CreateRecursiveDirs(str_dir.c_str(), mode);
428   }
429   const string dir(strutil::NormalizePath(str_dir));
430   const int result = ::mkdir(dir.c_str(), mode);
431   if ( result != 0 && GetLastSystemError() != EEXIST ) {
432     LOG_ERROR << "Failed to create dir: [" << dir << "] error: "
433               << GetLastSystemErrorDescription();
434     return false;
435   }
436   return true;
437 }
438
439 string MakeAbsolutePath(const char* path) {
440   const string strPath(strutil::StrTrim(string(path)));
441
442   // if "path" is already absolute, then there's nothing more to do
443   if ( strPath[0] == '/' ) {
444     return strPath;
445   }
446
447   // "path" is relative.
448
449   // find current working directory
450   char cwd[1024] = { 0, };
451   char* result = getcwd(cwd, sizeof(cwd));
452   if ( !result ) {
453     LOG_ERROR << " getcwd(..) failed: "
454               << GetLastSystemErrorDescription();
455     return strPath;
456   }
457
458   // append relative "path" to current working directory
459   const string full_path(strutil::NormalizePath(
460                              string(cwd) + "/" + strPath));
461   return full_path;
462 }
463
464 }
Note: See TracBrowser for help on using the browser.