0836d375920b7ea52739a11af5a719be0261052d
[libi2ncommon] / src / filefunc.cpp
1 /*
2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
4
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
7
8 As a special exception, if other files instantiate templates or use macros
9 or inline functions from this file, or you compile this file and link it
10 with other works to produce a work based on this file, this file
11 does not by itself cause the resulting work to be covered
12 by the GNU General Public License.
13
14 However the source code for this file must still be made available
15 in accordance with section (3) of the GNU General Public License.
16
17 This exception does not invalidate any other reasons why a work based
18 on this file might be covered by the GNU General Public License.
19 */
20 /***************************************************************************
21                           escape.cpp  -  escaping of strings
22                              -------------------
23     begin                : Sun Nov 14 1999
24     copyright            : (C) 1999 by Intra2net AG
25  ***************************************************************************/
26
27 #include <list>
28 #include <string>
29 #include <fstream>
30 #include <sstream>
31 #include <iostream>
32 #include <stdexcept>
33
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <dirent.h>
37 #include <pwd.h>
38 #include <grp.h>
39 #include <unistd.h>
40 #include <errno.h>
41 #include <string.h>
42
43 #include <boost/scoped_array.hpp>
44 #include "filefunc.hxx"
45 #include "stringfunc.hxx"
46
47 namespace I2n
48 {
49
50 using namespace std;
51
52 /*
53 ** implementation of Stat
54 */
55
56 Stat::Stat()
57 : Valid(false)
58 {
59    clear();
60 } // eo Stat::Stat()
61
62
63 Stat::Stat(const std::string& path, bool follow_links)
64 {
65     stat(path,follow_links);
66 } // eo Stat::Stat(const std::string&,bool)
67
68
69 Stat::~Stat()
70 {
71 } // eo Stat::~Stat()
72
73
74 /**
75  * @brief updates the internal data.
76  *
77  * In other words: stat()'s the file again.
78  */
79 void Stat::recheck()
80 {
81     if (! Path.empty())
82     {
83         // pass a copy of Path: otherwise clear() would leave an empty reference
84         stat(string(Path), FollowLinks);
85     }
86 } // eo Stat::recheck()
87
88
89 /**
90  * @brief calls stat() or lstat() to get the information for the given path
91  *  and stores that information.
92  * @param path the path which should be checked
93  * @param follow_links determine if (symbalic) links should be followed.
94  */
95 void Stat::stat(const std::string& path, bool follow_links)
96 {
97     clear();
98     Path= path;
99     FollowLinks= follow_links;
100     struct stat stat_info;
101     int res;
102     res = ( follow_links ? ::stat(path.c_str(), &stat_info) : ::lstat(path.c_str(), &stat_info) );
103     if ( 0 == res )
104     {
105         Device = stat_info.st_dev;
106         Inode  = stat_info.st_ino;
107         Mode   = (stat_info.st_mode & ~(S_IFMT));
108         NumLinks = stat_info.st_nlink;
109         Uid    = stat_info.st_uid;
110         Gid    = stat_info.st_gid;
111         DeviceType = stat_info.st_rdev;
112         Size   = stat_info.st_size;
113         Atime  = stat_info.st_atime;
114         Mtime  = stat_info.st_mtime;
115         Ctime  = stat_info.st_atime;
116
117         IsLink=          S_ISLNK( stat_info.st_mode );
118         IsRegular=       S_ISREG( stat_info.st_mode );
119         IsDirectory=     S_ISDIR( stat_info.st_mode );
120         IsCharacterDevice= S_ISCHR( stat_info.st_mode );
121         IsBlockDevice=   S_ISBLK( stat_info.st_mode );
122         IsFifo=          S_ISFIFO( stat_info.st_mode );
123         IsSocket=        S_ISSOCK( stat_info.st_mode );
124     }
125     Valid = (0 == res);
126 } // eo Stat::stat(const std::string&,bool)
127
128
129 /**
130  * @brief clears the internal data.
131  */
132 void Stat::clear()
133 {
134     Path.clear();
135     Valid= false;
136
137     Device = 0;
138     Inode  = 0;
139     Mode   = 0;
140     NumLinks = 0;
141     Uid    = 0;
142     Gid    = 0;
143     DeviceType = 0;
144     Size   = 0;
145     Atime  = 0;
146     Mtime  = 0;
147     Ctime  = 0;
148
149     IsLink= false;
150     IsRegular= false;
151     IsDirectory= false;
152     IsCharacterDevice= false;
153     IsBlockDevice= false;
154     IsFifo= false;
155     IsSocket= false;
156 } // eo Stat::clear()
157
158
159 /**
160  * @brief checks if another instance describes the same file.
161  * @param rhs the other instance.
162  * @return @a true iff the other instance describes the same file.
163  * @note
164  * The "same file" means that the files are located on the same device and use the same inode.
165  * They might still have two different directory entries (different paths)!
166  */
167 bool Stat::is_same_as(const Stat& rhs)
168 {
169     return Valid and rhs.Valid
170         and ( Device == rhs.Device)
171         and ( Inode == rhs.Inode);
172 } // eo Stat::is_same_as(const Stat& rhs);
173
174
175 /**
176  * @brief checks if this and the other instance describe the same device.
177  * @param rhs the other instance.
178  * @return @a true if we and the other instance describe a device and the same device.
179  *
180  * "Same device" means that the devices have the same type and the same major and minor id.
181  */
182 bool Stat::is_same_device_as(const Stat& rhs)
183 {
184     return is_device() and rhs.is_device()
185         and ( IsBlockDevice == rhs.IsBlockDevice )
186         and ( IsCharacterDevice == rhs.IsCharacterDevice )
187         and ( DeviceType == rhs.DeviceType);
188 } // eo Stat::is_same_device_as(const Stat&)
189
190 /**
191  * @brief check existence of a path.
192  * @param path path which should be tested.
193  * @return @a true iff path exists.
194  */
195 bool path_exists(const std::string& path)
196 {
197     struct stat stat_info;
198     int res = ::stat(path.c_str(), &stat_info);
199     if (res) return false;
200     return true;
201 } // eo path_exists(const std::string&)
202
203 /**
204  * @brief check existence of a regular file.
205  * @param path path which should be tested.
206  * @return @a true if path exists and is a regular file (or a link pointing to a regular file).
207  * @note this checks for regular files; not for the pure exitsnace of a path; use pathExists() for that.
208  * @see pathExists
209  */
210 bool file_exists(const std::string& path)
211 {
212     struct stat stat_info;
213     int res = ::stat(path.c_str(), &stat_info);
214     if (res) return false;
215     return S_ISREG(stat_info.st_mode);
216 } // eo file_exists(const std::string&)
217
218 // TODO: Use Stat class
219 /**
220  * Get size of file
221  * @param name filename to get size for
222  * @return file size or -1 if file does not exist
223  */
224 long file_size (const string &name)
225 {
226     long iReturn = -1;
227
228     struct stat statbuff;
229
230     if (lstat(name.c_str(), &statbuff) < 0)
231         return -1;
232
233     if (!S_ISREG(statbuff.st_mode))
234         return -1;
235
236     iReturn=statbuff.st_size;
237
238     return iReturn;
239 }
240
241 /**
242  * @brief tests the last modification time stamp of a path.
243  * @param path path which should be tested.
244  * @return the last modification time or 0 if the path doen't exist.
245  */
246 time_t file_mtime(const std::string& path)
247 {
248     struct stat stat_info;
249     int res = ::stat(path.c_str(), &stat_info);
250     if (res) return 0;
251     return stat_info.st_mtime;
252 } // eo file_mtime(const std::string&)
253
254
255 /**
256  * @brief reads the contents of a directory.
257  * @param path the path to the directory whose contents should be read.
258  * @param[out] result the resulting list of names.
259  * @param include_dot_names determines if dot-files should be included in the list.
260  * @return @a true if reading the directory was succesful, @a false on error.
261  */
262 bool get_dir(
263     const std::string& path,
264     std::vector< std::string >& result,
265     bool include_dot_names )
266 {
267     DIR* dir = ::opendir( path.c_str());
268     if (!dir)
269     {
270         return false;
271     }
272     struct dirent store, *entry = NULL;
273     while (readdir_r(dir, &store, &entry) == 0 && entry != NULL)
274     {
275         std::string name( entry->d_name );
276         if (! include_dot_names && (name[0] == '.') )
277         {
278             continue;
279         }
280         result.push_back( name );
281     }
282     ::closedir(dir);
283     return true;
284 } // eo get_dir(const std::string&,std::vector< std::string >&,bool)
285
286
287 /**
288  * @brief reads the contents of a directory
289  * @param path the path to the directory whose contents should be read.
290  * @param include_dot_names determines if dot-files should be included in the list.
291  * @return the list of names (empty on error).
292  */
293 std::vector< std::string > get_dir(const std::string& path, bool include_dot_names )
294 {
295     std::vector< std::string > result;
296     get_dir(path,result,include_dot_names);
297     return result;
298 } // eo get_dir(const std::string&,bool)
299
300
301
302 /**
303  * @brief removes a file from a filesystem.
304  * @param path path to the file.
305  * @return @a true if the unlink was successful.
306  */
307 bool unlink( const std::string& path )
308 {
309     int res = ::unlink( path.c_str() );
310     return (res == 0);
311 } // eo unlink(const std::string&)
312
313
314
315 /**
316  * @brief creates a symbolic link named @a link_name to @a target.
317  * @param target the target the link should point to.
318  * @param link_name the name of the link.
319  * @param force if @a true, the (file or link) @a link_name is removed if it exists.
320  * @return @a true iff the symlink was successfully created.
321  */
322 bool symlink(const std::string& target, const std::string& link_name, bool force)
323 {
324     int res= -1;
325     if (target.empty() or link_name.empty() or target == link_name)
326     {
327         // no, we don't do this!
328         return false;
329     }
330     std::string n_target;
331     if (target[0] == '/') // absolute target?
332     {
333         n_target= target;
334     }
335     else // relative target
336     {
337         // for stat'ing: prepend dir of link_name:
338         n_target= dirname(link_name)+"/"+ target;
339     }
340     Stat target_stat(n_target, false);
341     Stat link_name_stat(link_name, false);
342     if (target_stat.exists() && link_name_stat.exists())
343     {
344         if (link_name_stat.is_same_as(target_stat)
345             or link_name_stat.is_same_device_as(target_stat) )
346         {
347             return false;
348         }
349         //TODO: more consistency checks?!
350     }
351     if (link_name_stat.exists())
352     {
353         // the link name already exists.
354         if (force)
355         {
356             // "force" as given, so try to remove the link_name:
357             unlink(link_name);
358             // update the stat:
359             link_name_stat.recheck();
360         }
361     }
362     if (link_name_stat.exists())
363     {
364         // well, if the link_name still exists; we cannot create that link:
365         errno = EEXIST;
366         return false;
367     }
368     res= ::symlink(target.c_str(), link_name.c_str());
369     return (0 == res);
370 } // eo symlink(const std::string&,const std::string&,bool)
371
372
373
374 /**
375  * @brief reads the target of a symbolic link.
376  * @param path path to the symbolic link
377  * @return the target of the link or an empty string on error.
378  */
379 std::string read_link(const std::string& path)
380 {
381     errno= 0;
382     Stat stat(path,false);
383     if (!stat || !stat.is_link())
384     {
385         return std::string();
386     }
387     int buffer_size= PATH_MAX+1 + 128;
388     boost::scoped_array<char> buffer_ptr( new char[buffer_size] );
389     int res= ::readlink( path.c_str(), buffer_ptr.get(), buffer_size-1 );
390     if (res >= 0)
391     {
392         return std::string( buffer_ptr.get(), res );
393     }
394     return std::string();
395 } // eo read_link(const std::string&)
396
397
398
399 /**
400  * @brief returns content of a file as string.
401  *
402  * A simple (q'n'd) function for retrieving content of a file as string.<br>
403  * Also able to read special (but regular) files which don't provide a size when stat'ed
404  * (like files in the /proc filesystem).
405  *
406  * @param path path to the file.
407  * @return the content of the file as string (empty if file could be opened).
408  */
409 std::string read_file(const std::string& path)
410 {
411     Stat stat(path);
412     if (!stat.is_reg())
413     {
414         return std::string();
415     }
416     std::ifstream f( path.c_str(), std::ios::in | std::ios::binary );
417     std::string result;
418     if (f.is_open())
419     {
420         // NOTE: there are cases where we don't know the correct size (/proc files...)
421         // therefore we only use the size for reserving space if we know it, but don't
422         // use it when reading the file!
423         if (stat.size() > 0)
424         {
425             // if we know the size, we reserve enough space.
426             result.reserve( stat.size() );
427         }
428         char buffer[2048];
429         while (f.good())
430         {
431             f.read(buffer, sizeof(buffer));
432             result.append(buffer, f.gcount());
433         }
434     }
435     return result;
436 } // eo read_file(const std::string&)
437
438
439 /**
440  * @brief writes a string to a file.
441  * @param path path to the file
442  * @param data the data which should be written into the file
443  * @return @a true if the data was written to the file.
444  *
445  * A simple (q'n'd) function for writing a string to a file.
446  */
447 bool write_file(const std::string& path, const std::string& data)
448 {
449     std::ofstream f( path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
450     if (f.good())
451     {
452         f.write( data.data(), data.size() );
453         return f.good();
454     }
455     else
456     {
457         return false;
458     }
459 } // eo write_file(const std::string&,const std::string&)
460
461
462 /**
463  * Copy file in 4k blocks from source to target.
464  * Overwrites the target if it already exists.
465  *
466  * On error the target file gets removed.
467  *
468  * @param src source file
469  * @param dest target file
470  * @return true if all is ok, false on error
471  */
472 bool copy_file(const std::string& src, const std::string& dest)
473 {
474     std::ifstream input( src.c_str(), std::ios::in | std::ios::binary );
475     if (!input)
476         return false;
477
478     std::ofstream output( dest.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
479     if (!output)
480         return false;
481
482     // Out of disc space?
483     if (!copy_stream(input,output))
484     {
485         output.close();
486         unlink(dest);
487         return false;
488     }
489
490     return true;
491 }
492
493 /**
494  * Copy streams in 4k blocks.
495  *
496  * @param is source stream
497  * @param os target stream
498  * @return true if all is ok, false on error
499  */
500 bool copy_stream(std::istream& is, std::ostream& os)
501 {
502    if (!is)
503       return false;
504
505    if (!os)
506       return false;
507
508    char buffer[4096];
509    while (is.good())
510    {
511       is.read(buffer, sizeof(buffer));
512       os.write(buffer, is.gcount());
513
514       // Can't write?
515       if (!os.good())
516          return false;
517    }
518
519    return true;
520 }
521
522 /**
523  * @brief returns the filename part of a path (last component)
524  * @param path the path.
525  * @return the last component of the path.
526  */
527 std::string basename(const std::string& path)
528 {
529     std::string::size_type pos= path.rfind('/');
530     if (pos != std::string::npos)
531     {
532         return path.substr(pos+1);
533     }
534     return path;
535 } // eo basename(const std::string&)
536
537
538 /**
539  * @brief returns the directory part of a path.
540  * @param path the path.
541  * @return the directory part of the path.
542  */
543 std::string dirname(const std::string& path)
544 {
545     std::string::size_type pos= path.rfind('/');
546     if (pos != std::string::npos)
547     {
548         std::string result(path,0,pos);
549         if (result.empty())
550         {
551             return ".";
552         }
553         return result;
554     }
555     return ".";
556 } // eo dirname(const std::string&)
557
558
559 /**
560  * @brief normalizes a path.
561  *
562  * This method removes empty and "." elements.
563  * It also resolves ".." parts by removing previous path elements if possible.
564  * Leading ".." elements are preserved when a relative path was given; else they are removed.
565  * Trailing slashes are removed.
566  *
567  * @param path the path which should be normalized.
568  * @return the normalized path.
569  */
570
571 std::string normalize_path(const std::string& path)
572 {
573     if (path.empty())
574     {
575         return std::string();
576     }
577     // remember if the given path was absolute since this information vanishes when
578     // we split the path (since we split with omitting empty parts...)
579     bool is_absolute= (path[0]=='/');
580     std::list< std::string >  parts;
581     std::list< std::string >  result_parts;
582     
583     split_string(path,parts,"/",true);
584     
585     for(std::list< std::string >::const_iterator it_parts= parts.begin();
586         it_parts != parts.end();
587         ++it_parts)
588     {
589         std::string part(*it_parts); //convenience..
590         if (part == std::string(".") )
591         {
592             // single dot is "current path"; ignore!
593             continue;
594         }
595         if (part == std::string("..") )
596         {
597             // double dot is "one part back"
598             if (result_parts.empty())
599             {
600                 if (is_absolute)
601                 {
602                     // ignore since we cannot move behind / on absolute paths...
603                 }
604                 else
605                 {
606                     // on relative path, we need to store the "..":
607                     result_parts.push_back(part);
608                 }
609             }
610             else if (result_parts.back() == std::string("..") )
611             {
612                 // if last element was already "..", we need to store the new one again...
613                 // (PS: no need for "absolute" check; this can only be the case on relative path)
614                 result_parts.push_back(part);
615             }
616             else
617             {
618                 // remove last element.
619                 result_parts.pop_back();
620             }
621             continue;
622         }
623         result_parts.push_back(part);
624     }
625     std::string result;
626     if (is_absolute)
627     {
628         result= "/";
629     }
630     result+= join_string(result_parts,"/");
631     return result;
632 } // eo normalize_path(const std::string&)
633
634
635 /**
636  * @brief calls fsync on a given directory to sync all it's metadata
637  * @param path the path of the directory.
638  * @return true if successful
639  */
640 bool dirsync(const std::string& path)
641 {
642     // sync the directory the file is in
643     DIR* dir=opendir(path.c_str());
644     if (dir == NULL)
645         return false;
646
647     int ret=fsync(dirfd(dir));
648
649     closedir(dir);
650
651     return (ret==0);
652 }
653
654 /**
655  * @brief changes the file(/path) mode.
656  * @param path the path to change the mode for.
657  * @param mode the new file mode.
658  * @return @a true iff the file mode was sucessfully changed.
659  */
660 bool chmod(const std::string& path, int mode)
661 {
662     int res= ::chmod(path.c_str(), mode);
663     return (res==0);
664 } // eo chmod(const std::string&,int)
665
666
667 /**
668  * @brief changed the owner of a file(/path)
669  * @param path the path to change the owner for.
670  * @param user the new file owner.
671  * @param group the new file group.
672  * @return @a true iff the file owner was succesfully changed.
673  *
674  * @note
675  * the validity of user and group within the system is not checked.
676  * This is intentional since this way we can use id's which are not assigned.
677  */
678 bool chown(const std::string& path, const I2n::User& user, const I2n::Group& group)
679 {
680     uid_t uid= user.Uid;
681     if (uid<0) return false;
682     gid_t gid= group.Gid;
683     if (gid<0) gid= user.Gid;
684     if (gid<0) return false;
685     int res= ::chown( path.c_str(), uid, gid);
686     return (res==0);
687 } // eo chown(const std::string&,const User&,const Group&)
688
689 /**
690  * Recursive delete of files and directories
691  * @param path File or directory to delete
692  * @param error Will contain the error if the return value is false [optional]
693  * @return true on success, false otherwise
694  */
695 bool recursive_delete(const std::string &path, std::string *error)
696 {
697     bool rtn = true;
698
699     try {
700         struct stat my_stat;
701         if (stat(path.c_str(), &my_stat) != 0) {
702             throw runtime_error("can't stat " + path);
703         }
704
705         if (S_ISDIR(my_stat.st_mode)) {
706             DIR *dir = opendir(path.c_str());
707             if (!dir) {
708                 throw runtime_error("can't open directory " + path);
709             }
710
711             struct dirent store, *entry = NULL;
712             while (readdir_r(dir, &store, &entry) == 0 && entry != NULL)
713             {
714                 string filename = entry->d_name;
715                 if (filename == "." || filename == "..") {
716                     continue;
717                 }
718
719                 // Delete subdir or file.
720                 rtn = recursive_delete(path + "/" + filename, error);
721                 if (rtn == false) {
722                     break;
723                 }
724             }
725
726             closedir(dir);
727             if (!rmdir(path)) {
728                 throw runtime_error("can't remove directory " + path);
729             }
730         } else {
731             if (!unlink(path)) {
732                 throw runtime_error("can't unlink " + path);
733             }
734         }
735     } catch (exception &e) {
736         if (error) {
737             ostringstream out;
738             out << e.what() << " (" << strerror(errno) << ")";
739             *error = out.str();
740         }
741         rtn = false;
742     } catch (...) {
743         if (error) {
744             ostringstream out;
745             out << "unknown error (" << strerror(errno) << ")";
746             *error = out.str();
747         }
748         rtn = false;
749     }
750
751     return rtn;
752 } // eo recursive_delete(const std::string&,std::string*)
753
754 /**
755     Create a unique temporary directory from path_template.
756     @param Path template. The last six characters must be XXXXXX.
757     @param error Will contain the error if the return value is false [optional]
758     @return Name of new directory or empty string on error.
759 */
760 std::string mkdtemp(const std::string &path_template, std::string *error)
761 {
762     boost::scoped_array<char> buf( new char[path_template.size()+1] );
763     path_template.copy(buf.get(), path_template.size());
764     buf[path_template.size()]=0;
765
766     char *unique_dir = ::mkdtemp(buf.get());
767     if (!unique_dir)
768     {
769         if (error)
770             *error = strerror(errno);
771         return "";
772     }
773
774     // Scoped pointer is still valid
775     return std::string(unique_dir);
776 }
777
778 /**
779     Create directory
780     @param path Path to create
781     @param error Will contain the error if the return value is false [optional]
782     @return True on success, false on error
783 */
784 bool mkdir(const std::string &path, const mode_t &mode, std::string *error)
785 {
786     if ( ::mkdir(path.c_str(), mode) == 0)
787         return true;
788
789     if (error)
790         *error = strerror(errno);
791     return false;
792 }
793
794 /**
795     Remove directory
796     @param path Path to removed
797     @param error Will contain the error if the return value is false [optional]
798     @return True on successs, false otherwise
799 */
800 bool rmdir(const std::string &path, std::string *error)
801 {
802     if ( ::rmdir(path.c_str() ) == 0)
803         return true;
804
805     if (error)
806         *error = strerror(errno);
807     return false;
808 }
809
810 /// Small helper class for scoped free
811 class scoped_C_free
812 {
813 public:
814     scoped_C_free(void *ptr)
815         : pointer_to_free(ptr)
816     {
817     }
818
819     ~scoped_C_free()
820     {
821         free (pointer_to_free);
822         pointer_to_free = NULL;
823     }
824
825 private:
826     void *pointer_to_free;
827 };
828
829 /**
830     Get current working directory
831     @return Current working directory. Empty string on error.
832 */
833 std::string getcwd()
834 {
835     char *cwd = ::getcwd(NULL, 0);
836     if (!cwd)
837         return "";
838
839     // Make deallocation of cwd exception safe
840     scoped_C_free holder(cwd);
841
842     string current_dir(cwd);
843     return current_dir;
844 }
845
846 /**
847     Change current working directory
848     @param path Path to change to
849     @param error Will contain the error if the return value is false [optional]
850     @return True on successs, false otherwise
851 */
852 bool chdir(const std::string &path, std::string *error)
853 {
854     if ( ::chdir(path.c_str() ) == 0)
855         return true;
856
857     if (error)
858         *error = strerror(errno);
859     return false;
860 }
861
862 /**
863     Set file mode creation mask
864     @param mask Creation mask
865     @return Previous creation mask (function call always succeeds)
866 */
867 mode_t umask(mode_t mask)
868 {
869     return ::umask(mask);
870 }
871
872 } // eo namespace I2n