libi2ncommon: (tomj) modified filefunc according to our new style guide.
[libi2ncommon] / src / filefunc.cpp
1 /***************************************************************************
2                           escape.cpp  -  escaping of strings
3                              -------------------
4     begin                : Sun Nov 14 1999
5     copyright            : (C) 1999 by Intra2net AG
6     email                : info@intra2net.com
7  ***************************************************************************/
8
9 #include <list>
10 #include <string>
11 #include <fstream>
12 #include <sstream>
13 #include <iostream>
14 #include <stdexcept>
15
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 #include <dirent.h>
19 #include <pwd.h>
20 #include <grp.h>
21 #include <unistd.h>
22 #include <errno.h>
23
24 #include <boost/scoped_array.hpp>
25 #include "filefunc.hxx"
26 #include "stringfunc.hxx"
27
28 // TODO: Remove me once libi2ncommon is completly switched to I2n
29 using namespace i2n;
30
31 namespace I2n
32 {
33
34 using namespace std;
35
36 /*
37 ** implementation of Stat
38 */
39
40 Stat::Stat()
41 : Valid(false)
42 {
43    clear();
44 } // eo Stat::Stat()
45
46
47 Stat::Stat(const std::string& path, bool follow_links)
48 {
49     stat(path,follow_links);
50 } // eo Stat::Stat(const std::string&,bool)
51
52
53 Stat::~Stat()
54 {
55 } // eo Stat::~Stat()
56
57
58 /**
59  * @brief updates the internal data.
60  *
61  * In other words: stat()'s the file again.
62  */
63 void Stat::recheck()
64 {
65     if (! Path.empty())
66     {
67         stat(Path, FollowLinks);
68     }
69 } // eo Stat::recheck()
70
71
72 /**
73  * @brief calls stat() or lstat() to get the information for the given path
74  *  and stores that information.
75  * @param path the path which should be checked
76  * @param follow_links determine if (symbalic) links should be followed.
77  */
78 void Stat::stat(const std::string& path, bool follow_links)
79 {
80     clear();
81     Path= path;
82     FollowLinks= follow_links;
83     struct stat stat_info;
84     int res;
85     res = ( follow_links ? ::stat(path.c_str(), &stat_info) : ::lstat(path.c_str(), &stat_info) );
86     if ( 0 == res )
87     {
88         Device = stat_info.st_dev;
89         Inode  = stat_info.st_ino;
90         Mode   = (stat_info.st_mode & ~(S_IFMT));
91         NumLinks = stat_info.st_nlink;
92         Uid    = stat_info.st_uid;
93         Gid    = stat_info.st_gid;
94         DeviceType = stat_info.st_rdev;
95         Size   = stat_info.st_size;
96         Atime  = stat_info.st_atime;
97         Mtime  = stat_info.st_mtime;
98         Ctime  = stat_info.st_atime;
99
100         IsLink=          S_ISLNK( stat_info.st_mode );
101         IsRegular=       S_ISREG( stat_info.st_mode );
102         IsDirectory=     S_ISDIR( stat_info.st_mode );
103         IsCharacterDevice= S_ISCHR( stat_info.st_mode );
104         IsBlockDevice=  S_ISBLK( stat_info.st_mode );
105         IsFifo=          S_ISFIFO( stat_info.st_mode );
106         IsSocket=        S_ISSOCK( stat_info.st_mode );
107     }
108     Valid = (0 == res);
109 } // eo Stat::stat(const std::string&,bool)
110
111
112 /**
113  * @brief clears the internal data.
114  */
115 void Stat::clear()
116 {
117     Path.clear();
118     Valid= false;
119
120     Device = 0;
121     Inode  = 0;
122     Mode   = 0;
123     NumLinks = 0;
124     Uid    = 0;
125     Gid    = 0;
126     DeviceType = 0;
127     Size   = 0;
128     Atime  = 0;
129     Mtime  = 0;
130     Ctime  = 0;
131
132     IsLink= false;
133     IsRegular= false;
134     IsDirectory= false;
135     IsCharacterDevice= false;
136     IsBlockDevice= false;
137     IsFifo= false;
138     IsSocket= false;
139 } // eo Stat::clear()
140
141
142 /**
143  * @brief checks if another instance describes the same file.
144  * @param rhs the other instance.
145  * @return @a true iff the other instance describes the same file.
146  * @note
147  * The "same file" means that the files are located on the same device and use the same inode.
148  * They might still have two different directory entries (different paths)!
149  */
150 bool Stat::is_same_as(const Stat& rhs)
151 {
152     return Valid and rhs.Valid
153         and ( Device == rhs.Device)
154         and ( Inode == rhs.Inode);
155 } // eo Stat::is_same_as(const Stat& rhs);
156
157
158 /**
159  * @brief checks if this and the other instance describe the same device.
160  * @param rhs the other instance.
161  * @return @a true if we and the other instance describe a device and the same device.
162  *
163  * "Same device" means that the devices have the same type and the same major and minor id.
164  */
165 bool Stat::is_same_device_as(const Stat& rhs)
166 {
167     return is_device() and rhs.is_device()
168         and ( IsBlockDevice == rhs.IsBlockDevice )
169         and ( IsCharacterDevice == rhs.IsCharacterDevice )
170         and ( DeviceType == rhs.DeviceType);
171 } // eo Stat::is_same_device_as(const Stat&)
172
173 /**
174  * @brief check existence of a path.
175  * @param path path which should be tested.
176  * @return @a true iff path exists.
177  */
178 bool path_exists(const std::string& path)
179 {
180     struct stat stat_info;
181     int res = ::stat(path.c_str(), &stat_info);
182     if (res) return false;
183     return true;
184 } // eo path_exists(const std::string&)
185
186 /**
187  * @brief check existence of a regular file.
188  * @param path path which should be tested.
189  * @return @a true if path exists and is a regular file (or a link pointing to a regular file).
190  * @note this checks for regular files; not for the pure exitsnace of a path; use pathExists() for that.
191  * @see pathExists
192  */
193 bool file_exists(const std::string& path)
194 {
195     struct stat stat_info;
196     int res = ::stat(path.c_str(), &stat_info);
197     if (res) return false;
198     return S_ISREG(stat_info.st_mode);
199 } // eo file_exists(const std::string&)
200
201 // TODO: Use Stat class
202 /**
203  * Get size of file
204  * @param name filename to get size for
205  * @return file size or -1 if file does not exist
206  */
207 long file_size (const string &name)
208 {
209     long iReturn = -1;
210
211     struct stat statbuff;
212
213     if (lstat(name.c_str(), &statbuff) < 0)
214         return -1;
215
216     if (!S_ISREG(statbuff.st_mode))
217         return -1;
218
219     iReturn=statbuff.st_size;
220
221     return iReturn;
222 }
223
224 /**
225  * @brief tests the last modification time stamp of a path.
226  * @param path path which should be tested.
227  * @return the last modification time or 0 if the path doen't exist.
228  */
229 time_t file_mtime(const std::string& path)
230 {
231     struct stat stat_info;
232     int res = ::stat(path.c_str(), &stat_info);
233     if (res) return 0;
234     return stat_info.st_mtime;
235 } // eo fileMTime(const std::string&)
236
237
238 /**
239  * @brief reads the contents of a directory.
240  * @param path the path to the directory whose contents should be read.
241  * @param[out] result the resulting list of names.
242  * @param include_dot_names determines if dot-files should be included in the list.
243  * @return @a true if reading the directory was succesful, @a false on error.
244  */
245 bool get_dir(
246     const std::string& path,
247     std::vector< std::string >& result,
248     bool include_dot_names )
249 {
250     DIR* dir = ::opendir( path.c_str());
251     if (!dir)
252     {
253         return false;
254     }
255     struct dirent *entry;
256     while ( NULL != (entry = ::readdir(dir)) )
257     {
258         std::string name( entry->d_name );
259         if (! include_dot_names && (name[0] == '.') )
260         {
261             continue;
262         }
263         result.push_back( name );
264     }
265     ::closedir(dir);
266     return true;
267 } // eo getDir(const std::string&,std::vector< std::string >&,bool)
268
269
270 /**
271  * @brief reads the contents of a directory
272  * @param path the path to the directory whose contents should be read.
273  * @param include_dot_names determines if dot-files should be included in the list.
274  * @return the list of names (empty on error).
275  */
276 std::vector< std::string > get_dir(const std::string& path, bool include_dot_names )
277 {
278     std::vector< std::string > result;
279     get_dir(path,result,include_dot_names);
280     return result;
281 } // eo getDir(const std::string&,bool)
282
283
284
285 /**
286  * @brief removes a file from a filesystem.
287  * @param path path to the file.
288  * @return @a true iff the unlink was successful.
289  */
290 bool unlink( const std::string& path )
291 {
292     int res = ::unlink( path.c_str() );
293     return (res == 0);
294 } // eo unlink(const std::string&)
295
296
297
298 /**
299  * @brief creates a symbolic link named @a link_name to @a target.
300  * @param target the target the link should point to.
301  * @param link_name the name of the link.
302  * @param force if @a true, the (file or link) @a link_name is removed if it exists.
303  * @return @a true iff the symlink was successfully created.
304  */
305 bool symlink(const std::string& target, const std::string& link_name, bool force)
306 {
307     int res= -1;
308     if (target.empty() or link_name.empty() or target == link_name)
309     {
310         // no, we don't do this!
311         return false;
312     }
313     std::string n_target;
314     if (target[0] == '/') // absolute target?
315     {
316         n_target= target;
317     }
318     else // relative target
319     {
320         // for stat'ing: prepend dir of link_name:
321         n_target= dirname(link_name)+"/"+ target;
322     }
323     Stat target_stat(n_target, false);
324     Stat link_name_stat(link_name, false);
325     if (target_stat.exists() && link_name_stat.exists())
326     {
327         if (link_name_stat.is_same_as(target_stat)
328             or link_name_stat.is_same_device_as(target_stat) )
329         {
330             return false;
331         }
332         //TODO: more consistency checks?!
333     }
334     if (link_name_stat.exists())
335     {
336         // the link name already exists.
337         if (force)
338         {
339             // "force" as given, so try to remove the link_name:
340             unlink(link_name);
341             // update the stat:
342             link_name_stat.recheck();
343         }
344     }
345     if (link_name_stat.exists())
346     {
347         // well, if the link_name still exists; we cannot create that link:
348         errno = EEXIST;
349         return false;
350     }
351     res= ::symlink(target.c_str(), link_name.c_str());
352     return (0 == res);
353 } // eo symlink(const std::string&,const std::string&,bool)
354
355
356
357 /**
358  * @brief reads the target of a symbolic link.
359  * @param path path to the symbolic link
360  * @return the target of the link or an empty string on error.
361  */
362 std::string read_link(const std::string& path)
363 {
364     errno= 0;
365     Stat stat(path,false);
366     if (!stat || !stat.is_link())
367     {
368         return std::string();
369     }
370     int buffer_size= PATH_MAX+1 + 128;
371     boost::scoped_array<char> buffer_ptr( new char[buffer_size] );
372     int res= ::readlink( path.c_str(), buffer_ptr.get(), buffer_size-1 );
373     if (res >= 0)
374     {
375         return std::string( buffer_ptr.get(), res );
376     }
377     return std::string();
378 } // eo readLink(const std::string&)
379
380
381
382 /**
383  * @brief returns content of a file as string.
384  *
385  * A simple (q'n'd) function for retrieving content of a file as string.<br>
386  * Also able to read special (but regular) files which don't provide a size when stat'ed
387  * (like files in the /proc filesystem).
388  *
389  * @param path path to the file.
390  * @return the content of the file as string (empty if file could be opened).
391  */
392 std::string read_file(const std::string& path)
393 {
394     Stat stat(path);
395     if (!stat.is_reg())
396     {
397         return std::string();
398     }
399     std::ifstream f( path.c_str(), std::ios::in | std::ios::binary );
400     std::string result;
401     if (f.is_open())
402     {
403         // NOTE: there are cases where we don't know the correct size (/proc files...)
404         // therefore we only use the size for reserving space if we know it, but don't
405         // use it when reading the file!
406         if (stat.size() > 0)
407         {
408             // if we know the size, we reserve enough space.
409             result.reserve( stat.size() );
410         }
411         char buffer[2048];
412         while (f.good())
413         {
414             f.read(buffer, sizeof(buffer));
415             result.append(buffer, f.gcount());
416         }
417     }
418     return result;
419 } // eo readFile
420
421
422 /**
423  * @brief writes a string to a file.
424  * @param path path to the file
425  * @param data the data which should be written into the file
426  * @return @a true if the data was written to the file.
427  *
428  * A simple (q'n'd) function for writing a string to a file.
429  */
430 bool write_file(const std::string& path, const std::string& data)
431 {
432     std::ofstream f( path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
433     if (f.good())
434     {
435         f.write( data.data(), data.size() );
436         return f.good();
437     }
438     else
439     {
440         return false;
441     }
442 } // eo writeFile
443
444
445 /**
446  * @brief returns the filename part of a path (last component)
447  * @param path the path.
448  * @return the last component of the path.
449  */
450 std::string basename(const std::string& path)
451 {
452     std::string::size_type pos= path.rfind('/');
453     if (pos != std::string::npos)
454     {
455         return path.substr(pos+1);
456     }
457     return path;
458 } // eo basename(const std::string&)
459
460
461 /**
462  * @brief returns the directory part of a path.
463  * @param path the path.
464  * @return the directory part of the path.
465  */
466 std::string dirname(const std::string& path)
467 {
468     std::string::size_type pos= path.rfind('/');
469     if (pos != std::string::npos)
470     {
471         std::string result(path,0,pos);
472         if (result.empty())
473         {
474             return ".";
475         }
476         return result;
477     }
478     return ".";
479 } // eo dirname(const std::string&)
480
481
482 /**
483  * @brief normalizes a path.
484  *
485  * This method removes empty and "." elements.
486  * It also resolves ".." parts by removing previous path elements if possible.
487  * Leading ".." elements are preserved when a relative path was given; else they are removed.
488  * Trailing slashes are removed.
489  *
490  * @param path the path which should be normalized.
491  * @return the normalized path.
492  */
493
494 std::string normalize_path(const std::string& path)
495 {
496     if (path.empty())
497     {
498         return std::string();
499     }
500     // remember if the given path was absolute since this information vanishes when
501     // we split the path (since we split with omitting empty parts...)
502     bool is_absolute= (path[0]=='/');
503     std::list< std::string >  parts;
504     std::list< std::string >  result_parts;
505     
506     splitString(path,parts,"/",true);
507     
508     for(std::list< std::string >::const_iterator it_parts= parts.begin();
509         it_parts != parts.end();
510         ++it_parts)
511     {
512         std::string part(*it_parts); //convenience..
513         if (part == std::string(".") )
514         {
515             // single dot is "current path"; ignore!
516             continue;
517         }
518         if (part == std::string("..") )
519         {
520             // double dot is "one part back"
521             if (result_parts.empty())
522             {
523                 if (is_absolute)
524                 {
525                     // ignore since we cannot move behind / on absolute paths...
526                 }
527                 else
528                 {
529                     // on relative path, we need to store the "..":
530                     result_parts.push_back(part);
531                 }
532             }
533             else if (result_parts.back() == std::string("..") )
534             {
535                 // if last element was already "..", we need to store the new one again...
536                 // (PS: no need for "absolute" check; this can only be the case on relative path)
537                 result_parts.push_back(part);
538             }
539             else
540             {
541                 // remove last element.
542                 result_parts.pop_back();
543             }
544             continue;
545         }
546         result_parts.push_back(part);
547     }
548     std::string result;
549     if (is_absolute)
550     {
551         result= "/";
552     }
553     result+= joinString(result_parts,"/");
554     return result;
555 } // eo normalizePath(const std::string&)
556
557
558 /**
559  * @brief changes the file(/path) mode.
560  * @param path the path to change the mode for.
561  * @param mode the new file mode.
562  * @return @a true iff the file mode was sucessfully changed.
563  */
564 bool chmod(const std::string& path, int mode)
565 {
566     int res= ::chmod(path.c_str(), mode);
567     return (res==0);
568 } // eo chmod(const std::string&,int)
569
570
571 /**
572  * @brief changed the owner of a file(/path)
573  * @param path the path to change the owner for.
574  * @param user the new file owner.
575  * @param group the new file group.
576  * @return @a true iff the file owner was succesfully changed.
577  *
578  * @note
579  * the validity of user and group within the system is not checked.
580  * This is intentional since this way we can use id's which are not assigned.
581  */
582 bool chown(const std::string& path, const I2n::User& user, const I2n::Group& group)
583 {
584     uid_t uid= user.Uid;
585     if (uid<0) return false;
586     gid_t gid= group.Gid;
587     if (gid<0) gid= user.Gid;
588     if (gid<0) return false;
589     int res= ::chown( path.c_str(), uid, gid);
590     return (res==0);
591 } // eo chown(const std::string&,const User&,const Group&)
592
593 /**
594  * Recursive delete of files and directories
595  * @param path File or directory to delete
596  * @param error Will contain the error if the return value is false
597  * @return true on success, false otherwise
598  */
599 bool recursive_delete(const std::string &path, std::string *error)
600 {
601     bool rtn = true;
602
603     try {
604         struct stat my_stat;
605         if (stat(path.c_str(), &my_stat) != 0) {
606             throw runtime_error("can't stat " + path);
607         }
608
609         if (S_ISDIR(my_stat.st_mode)) {
610             DIR *dir = opendir(path.c_str());
611             if (!dir) {
612                 throw runtime_error("can't open directory " + path);
613             }
614
615             struct dirent *entry;
616             while ((entry = readdir(dir))) {
617                 string filename = entry->d_name;
618                 if (filename == "." || filename == "..") {
619                     continue;
620                 }
621
622                 // Delete subdir or file.
623                 rtn = recursive_delete(path + "/" + filename, error);
624                 if (rtn == false) {
625                     break;
626                 }
627             }
628
629             closedir(dir);
630             if (rmdir(path.c_str()) != 0) {
631                 throw runtime_error("can't remove directory " + path);
632             }
633         } else {
634             if (unlink(path.c_str()) != 0) {
635                 throw runtime_error("can't unlink " + path);
636             }
637         }
638     } catch (exception &e) {
639         if (error) {
640             ostringstream out;
641             out << e.what() << " (" << strerror(errno) << ")";
642             *error = out.str();
643         }
644         rtn = false;
645     } catch (...) {
646         if (error) {
647             ostringstream out;
648             out << "unknown error (" << strerror(errno) << ")";
649             *error = out.str();
650         }
651         rtn = false;
652     }
653
654     return rtn;
655 }
656
657 }