Fix comment
[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                           filefunc.cpp  -  functions for working with FS
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 <sys/statvfs.h>
37 #include <dirent.h>
38 #include <pwd.h>
39 #include <grp.h>
40 #include <unistd.h>
41 #include <errno.h>
42 #include <string.h>
43 #include <map>
44 #include <set>
45
46 #include <boost/scoped_array.hpp>
47 #include <boost/foreach.hpp>
48 #include "filefunc.hxx"
49 #include "stringfunc.hxx"
50
51 namespace I2n
52 {
53
54 using namespace std;
55
56 /*
57 ** implementation of Stat
58 */
59
60 Stat::Stat()
61     : FollowLinks(true)
62     , Valid(false)
63 {
64    clear();
65 } // eo Stat::Stat()
66
67
68 Stat::Stat(const std::string& path, bool follow_links)
69 {
70     stat(path,follow_links);
71 } // eo Stat::Stat(const std::string&,bool)
72
73
74 Stat::~Stat()
75 {
76 } // eo Stat::~Stat()
77
78
79 /**
80  * @brief updates the internal data.
81  *
82  * In other words: stat()'s the file again.
83  */
84 void Stat::recheck()
85 {
86     if (! Path.empty())
87     {
88         // pass a copy of Path: otherwise clear() would leave an empty reference
89         stat(string(Path), FollowLinks);
90     }
91 } // eo Stat::recheck()
92
93
94 /**
95  * @brief calls stat() or lstat() to get the information for the given path
96  *  and stores that information.
97  * @param path the path which should be checked
98  * @param follow_links determine if (symbalic) links should be followed.
99  */
100 void Stat::stat(const std::string& path, bool follow_links)
101 {
102     clear();
103     Path= path;
104     FollowLinks= follow_links;
105     struct stat stat_info;
106     int res;
107     res = ( follow_links ? ::stat(path.c_str(), &stat_info) : ::lstat(path.c_str(), &stat_info) );
108     if ( 0 == res )
109     {
110         Device = stat_info.st_dev;
111         Inode  = stat_info.st_ino;
112         Mode   = (stat_info.st_mode & ~(S_IFMT));
113         NumLinks = stat_info.st_nlink;
114         Uid    = stat_info.st_uid;
115         Gid    = stat_info.st_gid;
116         DeviceType = stat_info.st_rdev;
117         Size   = stat_info.st_size;
118         Atime  = stat_info.st_atime;
119         Mtime  = stat_info.st_mtime;
120         Ctime  = stat_info.st_atime;
121
122         // the stat(2) manpage for linux defines that st_blocks is given in a number of 512-byte-blocks.
123         BytesOnDisk = stat_info.st_blocks;
124         BytesOnDisk*=(long long)512;
125
126         IsLink=          S_ISLNK( stat_info.st_mode );
127         IsRegular=       S_ISREG( stat_info.st_mode );
128         IsDirectory=     S_ISDIR( stat_info.st_mode );
129         IsCharacterDevice= S_ISCHR( stat_info.st_mode );
130         IsBlockDevice=   S_ISBLK( stat_info.st_mode );
131         IsFifo=          S_ISFIFO( stat_info.st_mode );
132         IsSocket=        S_ISSOCK( stat_info.st_mode );
133     }
134     Valid = (0 == res);
135 } // eo Stat::stat(const std::string&,bool)
136
137
138 /**
139  * @brief clears the internal data.
140  */
141 void Stat::clear()
142 {
143     Path.clear();
144     Valid= false;
145
146     Device = 0;
147     Inode  = 0;
148     Mode   = 0;
149     NumLinks = 0;
150     Uid    = 0;
151     Gid    = 0;
152     DeviceType = 0;
153     Size   = 0;
154     BytesOnDisk = 0;
155     Atime  = 0;
156     Mtime  = 0;
157     Ctime  = 0;
158
159     IsLink= false;
160     IsRegular= false;
161     IsDirectory= false;
162     IsCharacterDevice= false;
163     IsBlockDevice= false;
164     IsFifo= false;
165     IsSocket= false;
166 } // eo Stat::clear()
167
168
169 /**
170  * @brief checks if another instance describes the same file.
171  * @param rhs the other instance.
172  * @return @a true iff the other instance describes the same file.
173  * @note
174  * The "same file" means that the files are located on the same device and use the same inode.
175  * They might still have two different directory entries (different paths)!
176  */
177 bool Stat::is_same_as(const Stat& rhs)
178 {
179     return Valid and rhs.Valid
180         and ( Device == rhs.Device)
181         and ( Inode == rhs.Inode);
182 } // eo Stat::is_same_as(const Stat& rhs);
183
184
185 /**
186  * @brief checks if this and the other instance describe the same device.
187  * @param rhs the other instance.
188  * @return @a true if we and the other instance describe a device and the same device.
189  *
190  * "Same device" means that the devices have the same type and the same major and minor id.
191  */
192 bool Stat::is_same_device_as(const Stat& rhs)
193 {
194     return is_device() and rhs.is_device()
195         and ( IsBlockDevice == rhs.IsBlockDevice )
196         and ( IsCharacterDevice == rhs.IsCharacterDevice )
197         and ( DeviceType == rhs.DeviceType);
198 } // eo Stat::is_same_device_as(const Stat&)
199
200 /**
201  * @brief check existence of a path.
202  * @param path path which should be tested.
203  * @return @a true iff path exists.
204  */
205 bool path_exists(const std::string& path)
206 {
207     struct stat stat_info;
208     int res = ::stat(path.c_str(), &stat_info);
209     if (res) return false;
210     return true;
211 } // eo path_exists(const std::string&)
212
213 /**
214  * @brief check existence of a regular file.
215  * @param path path which should be tested.
216  * @return @a true if path exists and is a regular file (or a link pointing to a regular file).
217  * @note this checks for regular files; not for the pure exitsnace of a path; use pathExists() for that.
218  * @see pathExists
219  */
220 bool file_exists(const std::string& path)
221 {
222     struct stat stat_info;
223     int res = ::stat(path.c_str(), &stat_info);
224     if (res) return false;
225     return S_ISREG(stat_info.st_mode);
226 } // eo file_exists(const std::string&)
227
228 // TODO: Use Stat class
229 /**
230  * Get size of file
231  * @param name filename to get size for
232  * @return file size or -1 if file does not exist
233  */
234 long file_size (const string &name)
235 {
236     long iReturn = -1;
237
238     struct stat statbuff;
239
240     if (lstat(name.c_str(), &statbuff) < 0)
241         return -1;
242
243     if (!S_ISREG(statbuff.st_mode))
244         return -1;
245
246     iReturn=statbuff.st_size;
247
248     return iReturn;
249 }
250
251 /**
252  * @brief tests the last modification time stamp of a path.
253  * @param path path which should be tested.
254  * @return the last modification time or 0 if the path doen't exist.
255  */
256 time_t file_mtime(const std::string& path)
257 {
258     struct stat stat_info;
259     int res = ::stat(path.c_str(), &stat_info);
260     if (res) return 0;
261     return stat_info.st_mtime;
262 } // eo file_mtime(const std::string&)
263
264
265 /**
266  * @brief Check if two files differ
267  *
268  *        Note: Reads the whole file into memory
269  *              if the file size is identical.
270  *
271  * @param old_filename Filename of old file
272  * @param new_filename Filename of new file
273  *
274  * @return bool True if files differ, false otherwise.
275  *              If one file does not exist, also returns true
276  */
277 bool file_content_differs(const std::string &old_filename, const std::string &new_filename)
278 {
279     if (I2n::file_exists(old_filename) == false ||
280         I2n::file_exists(new_filename) == false)
281         return true;
282
283     // check if size differs
284     if (I2n::file_size(old_filename) != I2n::file_size(new_filename))
285         return true;
286
287     const std::string old_content = I2n::read_file(old_filename);
288     const std::string new_content = I2n::read_file(new_filename);
289
290     // check if content differs
291     if (old_content == new_content)
292         return false;
293
294     // Differ by default (fallback)
295     return true;
296 }
297
298
299 /**
300  * @brief reads the contents of a directory.
301  * @param path the path to the directory whose contents should be read.
302  * @param[out] result the resulting list of names.
303  * @param include_dot_names determines if dot-files should be included in the list.
304  * @return @a true if reading the directory was succesful, @a false on error.
305  */
306 bool get_dir(
307     const std::string& path,
308     std::vector< std::string >& result,
309     bool include_dot_names )
310 {
311     DIR* dir = ::opendir( path.c_str());
312     if (!dir)
313     {
314         return false;
315     }
316     struct dirent store, *entry = NULL;
317     while (readdir_r(dir, &store, &entry) == 0 && entry != NULL)
318     {
319         std::string name( entry->d_name );
320         if (! include_dot_names && (name[0] == '.') )
321         {
322             continue;
323         }
324         result.push_back( name );
325     }
326     ::closedir(dir);
327     return true;
328 } // eo get_dir(const std::string&,std::vector< std::string >&,bool)
329
330
331 /**
332  * @brief reads the contents of a directory
333  * @param path the path to the directory whose contents should be read.
334  * @param include_dot_names determines if dot-files should be included in the list.
335  * @return the list of names (empty on error).
336  */
337 std::vector< std::string > get_dir(const std::string& path, bool include_dot_names )
338 {
339     std::vector< std::string > result;
340     get_dir(path,result,include_dot_names);
341     return result;
342 } // eo get_dir(const std::string&,bool)
343
344
345
346 /**
347  * @brief removes a file from a filesystem.
348  * @param path path to the file.
349  * @return @a true if the unlink was successful.
350  */
351 bool unlink( const std::string& path )
352 {
353     int res = ::unlink( path.c_str() );
354     return (res == 0);
355 } // eo unlink(const std::string&)
356
357
358
359 /**
360  * @brief creates a symbolic link named @a link_name to @a target.
361  * @param target the target the link should point to.
362  * @param link_name the name of the link.
363  * @param force if @a true, the (file or link) @a link_name is removed if it exists.
364  * @return @a true iff the symlink was successfully created.
365  */
366 bool symlink(const std::string& target, const std::string& link_name, bool force)
367 {
368     int res= -1;
369     if (target.empty() or link_name.empty() or target == link_name)
370     {
371         // no, we don't do this!
372         return false;
373     }
374     std::string n_target;
375     if (target[0] == '/') // absolute target?
376     {
377         n_target= target;
378     }
379     else // relative target
380     {
381         // for stat'ing: prepend dir of link_name:
382         n_target= dirname(link_name)+"/"+ target;
383     }
384     Stat target_stat(n_target, false);
385     Stat link_name_stat(link_name, false);
386     if (target_stat.exists() && link_name_stat.exists())
387     {
388         if (link_name_stat.is_same_as(target_stat)
389             or link_name_stat.is_same_device_as(target_stat) )
390         {
391             return false;
392         }
393         //TODO: more consistency checks?!
394     }
395     if (link_name_stat.exists())
396     {
397         // the link name already exists.
398         if (force)
399         {
400             // "force" as given, so try to remove the link_name:
401             unlink(link_name);
402             // update the stat:
403             link_name_stat.recheck();
404         }
405     }
406     if (link_name_stat.exists())
407     {
408         // well, if the link_name still exists; we cannot create that link:
409         errno = EEXIST;
410         return false;
411     }
412     res= ::symlink(target.c_str(), link_name.c_str());
413     return (0 == res);
414 } // eo symlink(const std::string&,const std::string&,bool)
415
416
417
418 /**
419  * @brief reads the target of a symbolic link.
420  * @param path path to the symbolic link
421  * @return the target of the link or an empty string on error.
422  */
423 std::string read_link(const std::string& path)
424 {
425     errno= 0;
426     Stat stat(path,false);
427     if (!stat || !stat.is_link())
428     {
429         return std::string();
430     }
431     int buffer_size= PATH_MAX+1 + 128;
432     boost::scoped_array<char> buffer_ptr( new char[buffer_size] );
433     int res= ::readlink( path.c_str(), buffer_ptr.get(), buffer_size-1 );
434     if (res >= 0)
435     {
436         return std::string( buffer_ptr.get(), res );
437     }
438     return std::string();
439 } // eo read_link(const std::string&)
440
441
442
443 /**
444  * @brief returns content of a file as string.
445  *
446  * A simple (q'n'd) function for retrieving content of a file as string.<br>
447  * Also able to read special (but regular) files which don't provide a size when stat'ed
448  * (like files in the /proc filesystem).
449  *
450  * @param path path to the file.
451  * @return the content of the file as string (empty if file could be opened).
452  */
453 std::string read_file(const std::string& path)
454 {
455     Stat stat(path);
456     if (!stat.is_reg())
457     {
458         return std::string();
459     }
460     std::ifstream f( path.c_str(), std::ios::in | std::ios::binary );
461     std::string result;
462     if (f.is_open())
463     {
464         // NOTE: there are cases where we don't know the correct size (/proc files...)
465         // therefore we only use the size for reserving space if we know it, but don't
466         // use it when reading the file!
467         if (stat.size() > 0)
468         {
469             // if we know the size, we reserve enough space.
470             result.reserve( stat.size() );
471         }
472         char buffer[2048];
473         while (f.good())
474         {
475             f.read(buffer, sizeof(buffer));
476             result.append(buffer, f.gcount());
477         }
478     }
479     return result;
480 } // eo read_file(const std::string&)
481
482
483 /**
484  * @brief writes a string to a file.
485  * @param path path to the file
486  * @param data the data which should be written into the file
487  * @param trunc set the trunc flag when opening the file. Do not use for files in /proc and /sys
488  * @return @a true if the data was written to the file.
489  *
490  * A simple (q'n'd) function for writing a string to a file.
491  */
492 bool write_file(const std::string& path, const std::string& data, bool trunc)
493 {
494     // set the correct openmode flags
495     std::ios_base::openmode flags = std::ios::out | std::ios::binary;
496     if (trunc)
497         flags |= std::ios::trunc;
498     
499     std::ofstream f( path.c_str(), flags);
500     if (f.good())
501     {
502         f.write( data.data(), data.size() );
503         return f.good();
504     }
505     else
506     {
507         return false;
508     }
509 } // eo write_file(const std::string&,const std::string&)
510
511
512 /**
513  * Copy file in 4k blocks from source to target.
514  * Overwrites the target if it already exists.
515  *
516  * On error the target file gets removed.
517  *
518  * @param src source file
519  * @param dest target file
520  * @return true if all is ok, false on error
521  */
522 bool copy_file(const std::string& src, const std::string& dest)
523 {
524     std::ifstream input( src.c_str(), std::ios::in | std::ios::binary );
525     if (!input)
526         return false;
527
528     std::ofstream output( dest.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
529     if (!output)
530         return false;
531
532     // Out of disc space?
533     if (!copy_stream(input,output))
534     {
535         output.close();
536         unlink(dest);
537         return false;
538     }
539
540     return true;
541 }
542
543 /**
544  * Copy streams in 4k blocks.
545  *
546  * @param is source stream
547  * @param os target stream
548  * @return true if all is ok, false on error
549  */
550 bool copy_stream(std::istream& is, std::ostream& os)
551 {
552    if (!is)
553       return false;
554
555    if (!os)
556       return false;
557
558    char buffer[4096];
559    while (is.good())
560    {
561       is.read(buffer, sizeof(buffer));
562       os.write(buffer, is.gcount());
563
564       // Can't write?
565       if (!os.good())
566          return false;
567    }
568
569    return true;
570 }
571
572 /**
573  * @brief returns the filename part of a path (last component)
574  * @param path the path.
575  * @return the last component of the path.
576  */
577 std::string basename(const std::string& path)
578 {
579     std::string::size_type pos= path.rfind('/');
580     if (pos != std::string::npos)
581     {
582         return path.substr(pos+1);
583     }
584     return path;
585 } // eo basename(const std::string&)
586
587
588 /**
589  * @brief returns the directory part of a path.
590  * @param path the path.
591  * @return the directory part of the path.
592  */
593 std::string dirname(const std::string& path)
594 {
595     std::string::size_type pos= path.rfind('/');
596     if (pos != std::string::npos)
597     {
598         std::string result(path,0,pos);
599         if (result.empty())
600         {
601             return ".";
602         }
603         return result;
604     }
605     return ".";
606 } // eo dirname(const std::string&)
607
608
609 /**
610  * @brief normalizes a path.
611  *
612  * This method removes empty and "." elements.
613  * It also resolves ".." parts by removing previous path elements if possible.
614  * Leading ".." elements are preserved when a relative path was given; else they are removed.
615  * Trailing slashes are removed.
616  *
617  * @param path the path which should be normalized.
618  * @return the normalized path.
619  */
620
621 std::string normalize_path(const std::string& path)
622 {
623     if (path.empty())
624     {
625         return std::string();
626     }
627     // remember if the given path was absolute since this information vanishes when
628     // we split the path (since we split with omitting empty parts...)
629     bool is_absolute= (path[0]=='/');
630     std::list< std::string >  parts;
631     std::list< std::string >  result_parts;
632     
633     split_string(path,parts,"/",true);
634     
635     for(std::list< std::string >::const_iterator it_parts= parts.begin();
636         it_parts != parts.end();
637         ++it_parts)
638     {
639         std::string part(*it_parts); //convenience..
640         if (part == std::string(".") )
641         {
642             // single dot is "current path"; ignore!
643             continue;
644         }
645         if (part == std::string("..") )
646         {
647             // double dot is "one part back"
648             if (result_parts.empty())
649             {
650                 if (is_absolute)
651                 {
652                     // ignore since we cannot move behind / on absolute paths...
653                 }
654                 else
655                 {
656                     // on relative path, we need to store the "..":
657                     result_parts.push_back(part);
658                 }
659             }
660             else if (result_parts.back() == std::string("..") )
661             {
662                 // if last element was already "..", we need to store the new one again...
663                 // (PS: no need for "absolute" check; this can only be the case on relative path)
664                 result_parts.push_back(part);
665             }
666             else
667             {
668                 // remove last element.
669                 result_parts.pop_back();
670             }
671             continue;
672         }
673         result_parts.push_back(part);
674     }
675     std::string result;
676     if (is_absolute)
677     {
678         result= "/";
679     }
680     result+= join_string(result_parts,"/");
681     return result;
682 } // eo normalize_path(const std::string&)
683
684
685 /**
686  * @brief calls fsync on a given directory to sync all it's metadata
687  * @param path the path of the directory.
688  * @return true if successful
689  */
690 bool dirsync(const std::string& path)
691 {
692     // sync the directory the file is in
693     DIR* dir=opendir(path.c_str());
694     if (dir == NULL)
695         return false;
696
697     int ret=fsync(dirfd(dir));
698
699     closedir(dir);
700
701     return (ret==0);
702 }
703
704 /**
705  * @brief changes the file(/path) mode.
706  * @param path the path to change the mode for.
707  * @param mode the new file mode.
708  * @return @a true iff the file mode was sucessfully changed.
709  */
710 bool chmod(const std::string& path, int mode)
711 {
712     int res= ::chmod(path.c_str(), mode);
713     return (res==0);
714 } // eo chmod(const std::string&,int)
715
716
717 /**
718  * @brief changed the owner of a file(/path)
719  * @param path the path to change the owner for.
720  * @param user the new file owner.
721  * @param group the new file group.
722  * @return @a true iff the file owner was succesfully changed.
723  *
724  * @note
725  * the validity of user and group within the system is not checked.
726  * This is intentional since this way we can use id's which are not assigned.
727  */
728 bool chown(const std::string& path, const I2n::User& user, const I2n::Group& group)
729 {
730     uid_t uid= user.Uid;
731     if (uid<0) return false;
732     gid_t gid= group.Gid;
733     if (gid<0) gid= user.Gid;
734     if (gid<0) return false;
735     int res= ::chown( path.c_str(), uid, gid);
736     return (res==0);
737 } // eo chown(const std::string&,const User&,const Group&)
738
739 /**
740  * Recursive delete of files and directories
741  * @param path File or directory to delete
742  * @param keep_parent_dir Keep parent directory (=empty out directory) [optional]
743  * @param error Will contain the error if the return value is false [optional]
744  * @return true on success, false otherwise
745  */
746 bool recursive_delete(const std::string &path,
747                       bool keep_parent_dir,
748                       std::string *error)
749 {
750     bool rtn = true;
751
752     try
753     {
754         Stat sp(path, false);
755         if (!sp)
756             throw runtime_error("can't stat " + path);
757
758         if (sp.is_directory())
759         {
760             std::vector<std::string> dirents = get_dir(path, false);
761             BOOST_FOREACH(const std::string &filename, dirents)
762             {
763                 // Delete subdir or file.
764                 rtn = recursive_delete(path + "/" + filename, false, error);
765                 if (rtn == false)
766                     break;
767             }
768
769             if (keep_parent_dir == false && !rmdir(path))
770                 throw runtime_error("can't remove directory " + path);
771         }
772         else
773         {
774             if (!unlink(path))
775                 throw runtime_error("can't unlink " + path);
776         }
777     }
778     catch (exception &e)
779     {
780         if (error)
781         {
782             ostringstream out;
783             out << e.what() << " (" << strerror(errno) << ")";
784             *error = out.str();
785         }
786         rtn = false;
787     }
788     catch (...)
789     {
790         if (error)
791         {
792             ostringstream out;
793             out << "unknown error (" << strerror(errno) << ")";
794             *error = out.str();
795         }
796         rtn = false;
797     }
798
799     return rtn;
800 } // eo recursive_delete(const std::string&,std::string*)
801
802 /**
803     Create a unique temporary directory from path_template.
804     @param Path template. The last six characters must be XXXXXX.
805     @param error Will contain the error if the return value is empty [optional]
806     @return Name of new directory or empty string on error.
807
808     @seealso: classes in tmpfstream which offer functionality based on mkstemp
809 */
810 std::string mkdtemp(const std::string &path_template, std::string *error)
811 {
812     boost::scoped_array<char> buf( new char[path_template.size()+1] );
813     path_template.copy(buf.get(), path_template.size());
814     buf[path_template.size()]=0;
815
816     char *unique_dir = ::mkdtemp(buf.get());
817     if (!unique_dir)
818     {
819         if (error)
820             *error = strerror(errno);
821         return "";
822     }
823
824     // Scoped pointer is still valid
825     return std::string(unique_dir);
826 }
827
828 /**
829     Create directory
830     @param path Path to create
831     @param error Will contain the error if the return value is false [optional]
832     @return True on success, false on error
833 */
834 bool mkdir(const std::string &path, const mode_t &mode, std::string *error)
835 {
836     if ( ::mkdir(path.c_str(), mode) == 0)
837         return true;
838
839     if (error)
840         *error = strerror(errno);
841     return false;
842 }
843
844 /**
845     Remove directory
846     @param path Path to removed
847     @param error Will contain the error if the return value is false [optional]
848     @return True on successs, false otherwise
849 */
850 bool rmdir(const std::string &path, std::string *error)
851 {
852     if ( ::rmdir(path.c_str() ) == 0)
853         return true;
854
855     if (error)
856         *error = strerror(errno);
857     return false;
858 }
859
860 /// Small helper class for scoped free
861 class scoped_C_free
862 {
863 public:
864     scoped_C_free(void *ptr)
865         : pointer_to_free(ptr)
866     {
867     }
868
869     ~scoped_C_free()
870     {
871         free (pointer_to_free);
872         pointer_to_free = NULL;
873     }
874
875 private:
876     void *pointer_to_free;
877 };
878
879 /**
880     Get current working directory
881     @return Current working directory. Empty string on error.
882 */
883 std::string getcwd()
884 {
885     char *cwd = ::getcwd(NULL, 0);
886     if (!cwd)
887         return "";
888
889     // Make deallocation of cwd exception safe
890     scoped_C_free holder(cwd);
891
892     string current_dir(cwd);
893     return current_dir;
894 }
895
896 /**
897     Change current working directory
898     @param path Path to change to
899     @param error Will contain the error if the return value is false [optional]
900     @return True on successs, false otherwise
901 */
902 bool chdir(const std::string &path, std::string *error)
903 {
904     if ( ::chdir(path.c_str() ) == 0)
905         return true;
906
907     if (error)
908         *error = strerror(errno);
909     return false;
910 }
911
912 /**
913     Set file mode creation mask
914     @param mask Creation mask
915     @return Previous creation mask (function call always succeeds)
916 */
917 mode_t umask(mode_t mask)
918 {
919     return ::umask(mask);
920 }
921
922
923 /**
924  * @brief Remove unlisted files
925  *
926  * @param directory Directory to look for files
927  * @param keep_files List of files or directories to keep
928  * @param prefix Filename prefix to match. Empty prefix matches all.
929  *
930  * @return bool True if the directory was scanned, false on error (directory not found, permission denied)
931  **/
932 bool remove_unlisted_files(const std::string &directory,
933                            const std::set<std::string> &keep_files,
934                            const std::string &prefix)
935 {
936     std::vector<std::string> content;
937     if (!get_dir(directory, content, false))
938         return false;
939
940     bool all_fine = true;
941     BOOST_FOREACH(const std::string &file, content)
942     {
943         // Check for filename prefix (if any)
944         if (!prefix.empty() && file.find(prefix) != 0)
945             continue;
946
947         // Check if file is whitelisted
948         if (keep_files.find(file) != keep_files.end())
949             continue;
950
951         // Try to unlink file. (Continue on error)
952         if (!unlink(directory + "/" + file))
953             all_fine = false;
954     }
955
956     return all_fine;
957 }
958
959 /**
960  * @brief Get free size in bytes on a given path or filename
961  *
962  * @param path Directory or filename to look in
963  *
964  * @return Number of bytes available to a regular user, -1 in case of an error
965  **/
966 long long get_free_diskspace(const std::string& path)
967 {
968     struct statvfs sf;
969
970     int looplimit=10000;
971     int ret;
972     while ( ((ret=statvfs(path.c_str(),&sf)) == -1) && (errno==EINTR) && looplimit > 0)
973         looplimit--;
974
975     if (ret==-1)
976     {
977         // a real error occured
978         return -1;
979     }
980
981     long long free_bytes=0;
982
983     // load block size
984     free_bytes=sf.f_bsize;
985
986     // multiply by number of free blocks accessible by normal users
987     // make sure we really multiply long long by long long and don't overflow at 2 GB
988     free_bytes*=(long long)sf.f_bavail;
989
990     return free_bytes;
991 }
992
993 namespace
994 {
995 // anonymous namespace to make du_internal inaccessible from outside
996
997 // internally used by du, do not use for other things
998 void du_internal(const std::string &path, long long &sum, std::map<dev_t, std::set<ino_t> > &counted_inodes)
999 {
1000
1001     Stat sp(path, false);      // don't dereference symlinks here
1002     if (!sp)
1003         throw runtime_error("can't stat " + path);
1004
1005     // make sure we don't count hardlinked files twice
1006     bool count_file=true;
1007
1008     // dirs can't be hardlinked, their nlink is the size of entries -> doesn't matter for us here
1009     if (!sp.is_directory() && sp.nlink() > 1)
1010     {
1011         // see if we have remembered this dev / inode combination
1012         if (counted_inodes[sp.device()].count(sp.inode()))
1013             count_file=false;
1014         else
1015             counted_inodes[sp.device()].insert(sp.inode());
1016     }
1017
1018     // always add the space used, even if we have a directory, symlink or whatever:
1019     // they need space on disk too
1020
1021     if (count_file)
1022         sum+=sp.bytes_on_disk();
1023
1024     if (sp.is_directory())
1025     {
1026         std::vector<std::string> dirents = get_dir(path, false);
1027         BOOST_FOREACH(const std::string &filename, dirents)
1028         {
1029             // calculate size of subdir or file
1030             du_internal(path + "/" + filename, sum, counted_inodes);
1031         }
1032     }
1033 }
1034
1035 } // eo anon namespace
1036
1037 /**
1038  * like du(1): return the number bytes used by a directory structure, counting hardlinked files only once
1039  * @param path File or directory to start counting recursively
1040  * @param error Will contain the error if the return value is -1 [optional]
1041  * @return size in bytes on success, -1 on error
1042  */
1043 long long du(const std::string &path, std::string *error)
1044 {
1045     long long sum = 0;
1046
1047     std::map<dev_t, std::set<ino_t> > counted_inodes;
1048
1049     try
1050     {
1051         du_internal(path, sum, counted_inodes);
1052     }
1053     catch (exception &e)
1054     {
1055         if (error)
1056         {
1057             ostringstream out;
1058             out << e.what() << " (" << strerror(errno) << ")";
1059             *error = out.str();
1060         }
1061         return -1;
1062     }
1063     catch (...)
1064     {
1065         if (error)
1066         {
1067             ostringstream out;
1068             out << "unknown error (" << strerror(errno) << ")";
1069             *error = out.str();
1070         }
1071         return -1;
1072     }
1073
1074     return sum;
1075 } // eo du
1076
1077
1078 } // eo namespace I2n