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