add new trunc option to write_file
[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  * @param trunc set the trunc flag when opening the file. Do not use for files in /proc and /sys
444  * @return @a true if the data was written to the file.
445  *
446  * A simple (q'n'd) function for writing a string to a file.
447  */
448 bool write_file(const std::string& path, const std::string& data, bool trunc)
449 {
450     // set the correct openmode flags
451     std::ios_base::openmode flags = std::ios::out | std::ios::binary;
452     if (trunc)
453         flags |= std::ios::trunc;
454     
455     std::ofstream f( path.c_str(), flags);
456     if (f.good())
457     {
458         f.write( data.data(), data.size() );
459         return f.good();
460     }
461     else
462     {
463         return false;
464     }
465 } // eo write_file(const std::string&,const std::string&)
466
467
468 /**
469  * Copy file in 4k blocks from source to target.
470  * Overwrites the target if it already exists.
471  *
472  * On error the target file gets removed.
473  *
474  * @param src source file
475  * @param dest target file
476  * @return true if all is ok, false on error
477  */
478 bool copy_file(const std::string& src, const std::string& dest)
479 {
480     std::ifstream input( src.c_str(), std::ios::in | std::ios::binary );
481     if (!input)
482         return false;
483
484     std::ofstream output( dest.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
485     if (!output)
486         return false;
487
488     // Out of disc space?
489     if (!copy_stream(input,output))
490     {
491         output.close();
492         unlink(dest);
493         return false;
494     }
495
496     return true;
497 }
498
499 /**
500  * Copy streams in 4k blocks.
501  *
502  * @param is source stream
503  * @param os target stream
504  * @return true if all is ok, false on error
505  */
506 bool copy_stream(std::istream& is, std::ostream& os)
507 {
508    if (!is)
509       return false;
510
511    if (!os)
512       return false;
513
514    char buffer[4096];
515    while (is.good())
516    {
517       is.read(buffer, sizeof(buffer));
518       os.write(buffer, is.gcount());
519
520       // Can't write?
521       if (!os.good())
522          return false;
523    }
524
525    return true;
526 }
527
528 /**
529  * @brief returns the filename part of a path (last component)
530  * @param path the path.
531  * @return the last component of the path.
532  */
533 std::string basename(const std::string& path)
534 {
535     std::string::size_type pos= path.rfind('/');
536     if (pos != std::string::npos)
537     {
538         return path.substr(pos+1);
539     }
540     return path;
541 } // eo basename(const std::string&)
542
543
544 /**
545  * @brief returns the directory part of a path.
546  * @param path the path.
547  * @return the directory part of the path.
548  */
549 std::string dirname(const std::string& path)
550 {
551     std::string::size_type pos= path.rfind('/');
552     if (pos != std::string::npos)
553     {
554         std::string result(path,0,pos);
555         if (result.empty())
556         {
557             return ".";
558         }
559         return result;
560     }
561     return ".";
562 } // eo dirname(const std::string&)
563
564
565 /**
566  * @brief normalizes a path.
567  *
568  * This method removes empty and "." elements.
569  * It also resolves ".." parts by removing previous path elements if possible.
570  * Leading ".." elements are preserved when a relative path was given; else they are removed.
571  * Trailing slashes are removed.
572  *
573  * @param path the path which should be normalized.
574  * @return the normalized path.
575  */
576
577 std::string normalize_path(const std::string& path)
578 {
579     if (path.empty())
580     {
581         return std::string();
582     }
583     // remember if the given path was absolute since this information vanishes when
584     // we split the path (since we split with omitting empty parts...)
585     bool is_absolute= (path[0]=='/');
586     std::list< std::string >  parts;
587     std::list< std::string >  result_parts;
588     
589     split_string(path,parts,"/",true);
590     
591     for(std::list< std::string >::const_iterator it_parts= parts.begin();
592         it_parts != parts.end();
593         ++it_parts)
594     {
595         std::string part(*it_parts); //convenience..
596         if (part == std::string(".") )
597         {
598             // single dot is "current path"; ignore!
599             continue;
600         }
601         if (part == std::string("..") )
602         {
603             // double dot is "one part back"
604             if (result_parts.empty())
605             {
606                 if (is_absolute)
607                 {
608                     // ignore since we cannot move behind / on absolute paths...
609                 }
610                 else
611                 {
612                     // on relative path, we need to store the "..":
613                     result_parts.push_back(part);
614                 }
615             }
616             else if (result_parts.back() == std::string("..") )
617             {
618                 // if last element was already "..", we need to store the new one again...
619                 // (PS: no need for "absolute" check; this can only be the case on relative path)
620                 result_parts.push_back(part);
621             }
622             else
623             {
624                 // remove last element.
625                 result_parts.pop_back();
626             }
627             continue;
628         }
629         result_parts.push_back(part);
630     }
631     std::string result;
632     if (is_absolute)
633     {
634         result= "/";
635     }
636     result+= join_string(result_parts,"/");
637     return result;
638 } // eo normalize_path(const std::string&)
639
640
641 /**
642  * @brief calls fsync on a given directory to sync all it's metadata
643  * @param path the path of the directory.
644  * @return true if successful
645  */
646 bool dirsync(const std::string& path)
647 {
648     // sync the directory the file is in
649     DIR* dir=opendir(path.c_str());
650     if (dir == NULL)
651         return false;
652
653     int ret=fsync(dirfd(dir));
654
655     closedir(dir);
656
657     return (ret==0);
658 }
659
660 /**
661  * @brief changes the file(/path) mode.
662  * @param path the path to change the mode for.
663  * @param mode the new file mode.
664  * @return @a true iff the file mode was sucessfully changed.
665  */
666 bool chmod(const std::string& path, int mode)
667 {
668     int res= ::chmod(path.c_str(), mode);
669     return (res==0);
670 } // eo chmod(const std::string&,int)
671
672
673 /**
674  * @brief changed the owner of a file(/path)
675  * @param path the path to change the owner for.
676  * @param user the new file owner.
677  * @param group the new file group.
678  * @return @a true iff the file owner was succesfully changed.
679  *
680  * @note
681  * the validity of user and group within the system is not checked.
682  * This is intentional since this way we can use id's which are not assigned.
683  */
684 bool chown(const std::string& path, const I2n::User& user, const I2n::Group& group)
685 {
686     uid_t uid= user.Uid;
687     if (uid<0) return false;
688     gid_t gid= group.Gid;
689     if (gid<0) gid= user.Gid;
690     if (gid<0) return false;
691     int res= ::chown( path.c_str(), uid, gid);
692     return (res==0);
693 } // eo chown(const std::string&,const User&,const Group&)
694
695 /**
696  * Recursive delete of files and directories
697  * @param path File or directory to delete
698  * @param error Will contain the error if the return value is false [optional]
699  * @return true on success, false otherwise
700  */
701 bool recursive_delete(const std::string &path, std::string *error)
702 {
703     bool rtn = true;
704
705     try {
706         struct stat my_stat;
707         if (stat(path.c_str(), &my_stat) != 0) {
708             throw runtime_error("can't stat " + path);
709         }
710
711         if (S_ISDIR(my_stat.st_mode)) {
712             DIR *dir = opendir(path.c_str());
713             if (!dir) {
714                 throw runtime_error("can't open directory " + path);
715             }
716
717             struct dirent store, *entry = NULL;
718             while (readdir_r(dir, &store, &entry) == 0 && entry != NULL)
719             {
720                 string filename = entry->d_name;
721                 if (filename == "." || filename == "..") {
722                     continue;
723                 }
724
725                 // Delete subdir or file.
726                 rtn = recursive_delete(path + "/" + filename, error);
727                 if (rtn == false) {
728                     break;
729                 }
730             }
731
732             closedir(dir);
733             if (!rmdir(path)) {
734                 throw runtime_error("can't remove directory " + path);
735             }
736         } else {
737             if (!unlink(path)) {
738                 throw runtime_error("can't unlink " + path);
739             }
740         }
741     } catch (exception &e) {
742         if (error) {
743             ostringstream out;
744             out << e.what() << " (" << strerror(errno) << ")";
745             *error = out.str();
746         }
747         rtn = false;
748     } catch (...) {
749         if (error) {
750             ostringstream out;
751             out << "unknown error (" << strerror(errno) << ")";
752             *error = out.str();
753         }
754         rtn = false;
755     }
756
757     return rtn;
758 } // eo recursive_delete(const std::string&,std::string*)
759
760 /**
761     Create a unique temporary directory from path_template.
762     @param Path template. The last six characters must be XXXXXX.
763     @param error Will contain the error if the return value is false [optional]
764     @return Name of new directory or empty string on error.
765 */
766 std::string mkdtemp(const std::string &path_template, std::string *error)
767 {
768     boost::scoped_array<char> buf( new char[path_template.size()+1] );
769     path_template.copy(buf.get(), path_template.size());
770     buf[path_template.size()]=0;
771
772     char *unique_dir = ::mkdtemp(buf.get());
773     if (!unique_dir)
774     {
775         if (error)
776             *error = strerror(errno);
777         return "";
778     }
779
780     // Scoped pointer is still valid
781     return std::string(unique_dir);
782 }
783
784 /**
785     Create directory
786     @param path Path to create
787     @param error Will contain the error if the return value is false [optional]
788     @return True on success, false on error
789 */
790 bool mkdir(const std::string &path, const mode_t &mode, std::string *error)
791 {
792     if ( ::mkdir(path.c_str(), mode) == 0)
793         return true;
794
795     if (error)
796         *error = strerror(errno);
797     return false;
798 }
799
800 /**
801     Remove directory
802     @param path Path to removed
803     @param error Will contain the error if the return value is false [optional]
804     @return True on successs, false otherwise
805 */
806 bool rmdir(const std::string &path, std::string *error)
807 {
808     if ( ::rmdir(path.c_str() ) == 0)
809         return true;
810
811     if (error)
812         *error = strerror(errno);
813     return false;
814 }
815
816 /// Small helper class for scoped free
817 class scoped_C_free
818 {
819 public:
820     scoped_C_free(void *ptr)
821         : pointer_to_free(ptr)
822     {
823     }
824
825     ~scoped_C_free()
826     {
827         free (pointer_to_free);
828         pointer_to_free = NULL;
829     }
830
831 private:
832     void *pointer_to_free;
833 };
834
835 /**
836     Get current working directory
837     @return Current working directory. Empty string on error.
838 */
839 std::string getcwd()
840 {
841     char *cwd = ::getcwd(NULL, 0);
842     if (!cwd)
843         return "";
844
845     // Make deallocation of cwd exception safe
846     scoped_C_free holder(cwd);
847
848     string current_dir(cwd);
849     return current_dir;
850 }
851
852 /**
853     Change current working directory
854     @param path Path to change to
855     @param error Will contain the error if the return value is false [optional]
856     @return True on successs, false otherwise
857 */
858 bool chdir(const std::string &path, std::string *error)
859 {
860     if ( ::chdir(path.c_str() ) == 0)
861         return true;
862
863     if (error)
864         *error = strerror(errno);
865     return false;
866 }
867
868 /**
869     Set file mode creation mask
870     @param mask Creation mask
871     @return Previous creation mask (function call always succeeds)
872 */
873 mode_t umask(mode_t mask)
874 {
875     return ::umask(mask);
876 }
877
878 } // eo namespace I2n