Rename get_dir_size() to get_dir_count()
[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     // code copied to get_dir_count; keep in sync
312     DIR* dir = ::opendir( path.c_str());
313     if (!dir)
314     {
315         return false;
316     }
317     struct dirent store, *entry = NULL;
318     while (readdir_r(dir, &store, &entry) == 0 && entry != NULL)
319     {
320         std::string name( entry->d_name );
321         if (! include_dot_names && (name[0] == '.') )
322         {
323             continue;
324         }
325         result.push_back( name );
326     }
327     ::closedir(dir);
328     return true;
329 } // eo get_dir(const std::string&,std::vector< std::string >&,bool)
330
331
332 /**
333  * @brief reads the contents of a directory
334  * @param path the path to the directory whose contents should be read.
335  * @param include_dot_names determines if dot-files should be included in the list.
336  * @return the list of names (empty on error).
337  */
338 std::vector< std::string > get_dir(const std::string& path, bool include_dot_names )
339 {
340     std::vector< std::string > result;
341     get_dir(path,result,include_dot_names);
342     return result;
343 } // eo get_dir(const std::string&,bool)
344
345
346 /**
347  * @brief count entries in directory, like get_dir(path, include_dot_names).size()
348  * @param path the path to the directory whose contents should be read.
349  * @param include_dot_names determines if dot-files should be included in the count.
350  * @return the number of entries in the directory; return -1 in case of error
351  */
352 int get_dir_count(const std::string& path, bool include_dot_names)
353 {
354     // code is a simplified copy of get_dir, above. Keep in sync
355     int result = 0;
356     DIR* dir = ::opendir( path.c_str());
357     if (!dir)
358         return -1;
359     struct dirent store, *entry = NULL;
360     while (readdir_r(dir, &store, &entry) == 0 && entry != NULL)
361     {
362         if (entry->d_name == NULL)
363             continue;    // should not happen
364         else if (! include_dot_names && (entry->d_name)[0] == '.')
365             continue;
366         ++result;
367     }
368     ::closedir(dir);
369     return result;
370 }
371
372
373 /**
374  * @brief removes a file from a filesystem.
375  * @param path path to the file.
376  * @return @a true if the unlink was successful.
377  */
378 bool unlink( const std::string& path )
379 {
380     int res = ::unlink( path.c_str() );
381     return (res == 0);
382 } // eo unlink(const std::string&)
383
384
385
386 /**
387  * @brief creates a symbolic link named @a link_name to @a target.
388  * @param target the target the link should point to.
389  * @param link_name the name of the link.
390  * @param force if @a true, the (file or link) @a link_name is removed if it exists.
391  * @return @a true iff the symlink was successfully created.
392  */
393 bool symlink(const std::string& target, const std::string& link_name, bool force)
394 {
395     int res= -1;
396     if (target.empty() or link_name.empty() or target == link_name)
397     {
398         // no, we don't do this!
399         return false;
400     }
401     std::string n_target;
402     if (target[0] == '/') // absolute target?
403     {
404         n_target= target;
405     }
406     else // relative target
407     {
408         // for stat'ing: prepend dir of link_name:
409         n_target= dirname(link_name)+"/"+ target;
410     }
411     Stat target_stat(n_target, false);
412     Stat link_name_stat(link_name, false);
413     if (target_stat.exists() && link_name_stat.exists())
414     {
415         if (link_name_stat.is_same_as(target_stat)
416             or link_name_stat.is_same_device_as(target_stat) )
417         {
418             return false;
419         }
420         //TODO: more consistency checks?!
421     }
422     if (link_name_stat.exists())
423     {
424         // the link name already exists.
425         if (force)
426         {
427             // "force" as given, so try to remove the link_name:
428             unlink(link_name);
429             // update the stat:
430             link_name_stat.recheck();
431         }
432     }
433     if (link_name_stat.exists())
434     {
435         // well, if the link_name still exists; we cannot create that link:
436         errno = EEXIST;
437         return false;
438     }
439     res= ::symlink(target.c_str(), link_name.c_str());
440     return (0 == res);
441 } // eo symlink(const std::string&,const std::string&,bool)
442
443
444
445 /**
446  * @brief reads the target of a symbolic link.
447  * @param path path to the symbolic link
448  * @return the target of the link or an empty string on error.
449  */
450 std::string read_link(const std::string& path)
451 {
452     errno= 0;
453     Stat stat(path,false);
454     if (!stat || !stat.is_link())
455     {
456         return std::string();
457     }
458     int buffer_size= PATH_MAX+1 + 128;
459     boost::scoped_array<char> buffer_ptr( new char[buffer_size] );
460     int res= ::readlink( path.c_str(), buffer_ptr.get(), buffer_size-1 );
461     if (res >= 0)
462     {
463         return std::string( buffer_ptr.get(), res );
464     }
465     return std::string();
466 } // eo read_link(const std::string&)
467
468
469
470 /**
471  * @brief returns content of a file as string.
472  *
473  * A simple (q'n'd) function for retrieving content of a file as string.<br>
474  * Also able to read special (but regular) files which don't provide a size when stat'ed
475  * (like files in the /proc filesystem).
476  *
477  * @param path path to the file.
478  * @return the content of the file as string (empty if file could be opened).
479  */
480 std::string read_file(const std::string& path)
481 {
482     Stat stat(path);
483     if (!stat.is_reg())
484     {
485         return std::string();
486     }
487     std::ifstream f( path.c_str(), std::ios::in | std::ios::binary );
488     std::string result;
489     if (f.is_open())
490     {
491         // NOTE: there are cases where we don't know the correct size (/proc files...)
492         // therefore we only use the size for reserving space if we know it, but don't
493         // use it when reading the file!
494         if (stat.size() > 0)
495         {
496             // if we know the size, we reserve enough space.
497             result.reserve( stat.size() );
498         }
499         char buffer[2048];
500         while (f.good())
501         {
502             f.read(buffer, sizeof(buffer));
503             result.append(buffer, f.gcount());
504         }
505     }
506     return result;
507 } // eo read_file(const std::string&)
508
509
510 /**
511  * @brief writes a string to a file.
512  * @param path path to the file
513  * @param data the data which should be written into the file
514  * @param trunc set the trunc flag when opening the file. Do not use for files in /proc and /sys
515  * @return @a true if the data was written to the file.
516  *
517  * A simple (q'n'd) function for writing a string to a file.
518  */
519 bool write_file(const std::string& path, const std::string& data, bool trunc)
520 {
521     // set the correct openmode flags
522     std::ios_base::openmode flags = std::ios::out | std::ios::binary;
523     if (trunc)
524         flags |= std::ios::trunc;
525     
526     std::ofstream f( path.c_str(), flags);
527     if (f.good())
528     {
529         f.write( data.data(), data.size() );
530         return f.good();
531     }
532     else
533     {
534         return false;
535     }
536 } // eo write_file(const std::string&,const std::string&)
537
538
539 /**
540  * Copy file in 4k blocks from source to target.
541  * Overwrites the target if it already exists.
542  *
543  * On error the target file gets removed.
544  *
545  * @param src source file
546  * @param dest target file
547  * @return true if all is ok, false on error
548  */
549 bool copy_file(const std::string& src, const std::string& dest)
550 {
551     std::ifstream input( src.c_str(), std::ios::in | std::ios::binary );
552     if (!input)
553         return false;
554
555     std::ofstream output( dest.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
556     if (!output)
557         return false;
558
559     // Out of disc space?
560     if (!copy_stream(input,output))
561     {
562         output.close();
563         unlink(dest);
564         return false;
565     }
566
567     return true;
568 }
569
570 /**
571  * Copy streams in 4k blocks.
572  *
573  * @param is source stream
574  * @param os target stream
575  * @return true if all is ok, false on error
576  */
577 bool copy_stream(std::istream& is, std::ostream& os)
578 {
579    if (!is)
580       return false;
581
582    if (!os)
583       return false;
584
585    char buffer[4096];
586    while (is.good())
587    {
588       is.read(buffer, sizeof(buffer));
589       os.write(buffer, is.gcount());
590
591       // Can't write?
592       if (!os.good())
593          return false;
594    }
595
596    return true;
597 }
598
599 /**
600  * @brief returns the filename part of a path (last component)
601  * @param path the path.
602  * @return the last component of the path.
603  */
604 std::string basename(const std::string& path)
605 {
606     std::string::size_type pos= path.rfind('/');
607     if (pos != std::string::npos)
608     {
609         return path.substr(pos+1);
610     }
611     return path;
612 } // eo basename(const std::string&)
613
614
615 /**
616  * @brief returns the directory part of a path.
617  * @param path the path.
618  * @return the directory part of the path.
619  */
620 std::string dirname(const std::string& path)
621 {
622     std::string::size_type pos= path.rfind('/');
623     if (pos != std::string::npos)
624     {
625         std::string result(path,0,pos);
626         if (result.empty())
627         {
628             return ".";
629         }
630         return result;
631     }
632     return ".";
633 } // eo dirname(const std::string&)
634
635
636 /**
637  * @brief normalizes a path.
638  *
639  * This method removes empty and "." elements.
640  * It also resolves ".." parts by removing previous path elements if possible.
641  * Leading ".." elements are preserved when a relative path was given; else they are removed.
642  * Trailing slashes are removed.
643  *
644  * @param path the path which should be normalized.
645  * @return the normalized path.
646  */
647
648 std::string normalize_path(const std::string& path)
649 {
650     if (path.empty())
651     {
652         return std::string();
653     }
654     // remember if the given path was absolute since this information vanishes when
655     // we split the path (since we split with omitting empty parts...)
656     bool is_absolute= (path[0]=='/');
657     std::list< std::string >  parts;
658     std::list< std::string >  result_parts;
659     
660     split_string(path,parts,"/",true);
661     
662     for(std::list< std::string >::const_iterator it_parts= parts.begin();
663         it_parts != parts.end();
664         ++it_parts)
665     {
666         std::string part(*it_parts); //convenience..
667         if (part == std::string(".") )
668         {
669             // single dot is "current path"; ignore!
670             continue;
671         }
672         if (part == std::string("..") )
673         {
674             // double dot is "one part back"
675             if (result_parts.empty())
676             {
677                 if (is_absolute)
678                 {
679                     // ignore since we cannot move behind / on absolute paths...
680                 }
681                 else
682                 {
683                     // on relative path, we need to store the "..":
684                     result_parts.push_back(part);
685                 }
686             }
687             else if (result_parts.back() == std::string("..") )
688             {
689                 // if last element was already "..", we need to store the new one again...
690                 // (PS: no need for "absolute" check; this can only be the case on relative path)
691                 result_parts.push_back(part);
692             }
693             else
694             {
695                 // remove last element.
696                 result_parts.pop_back();
697             }
698             continue;
699         }
700         result_parts.push_back(part);
701     }
702     std::string result;
703     if (is_absolute)
704     {
705         result= "/";
706     }
707     result+= join_string(result_parts,"/");
708     return result;
709 } // eo normalize_path(const std::string&)
710
711
712 /**
713  * @brief calls fsync on a given directory to sync all it's metadata
714  * @param path the path of the directory.
715  * @return true if successful
716  */
717 bool dirsync(const std::string& path)
718 {
719     // sync the directory the file is in
720     DIR* dir=opendir(path.c_str());
721     if (dir == NULL)
722         return false;
723
724     int ret=fsync(dirfd(dir));
725
726     closedir(dir);
727
728     return (ret==0);
729 }
730
731 /**
732  * @brief changes the file(/path) mode.
733  * @param path the path to change the mode for.
734  * @param mode the new file mode.
735  * @return @a true iff the file mode was sucessfully changed.
736  */
737 bool chmod(const std::string& path, int mode)
738 {
739     int res= ::chmod(path.c_str(), mode);
740     return (res==0);
741 } // eo chmod(const std::string&,int)
742
743
744 /**
745  * @brief changed the owner of a file(/path)
746  * @param path the path to change the owner for.
747  * @param user the new file owner.
748  * @param group the new file group.
749  * @return @a true iff the file owner was succesfully changed.
750  *
751  * @note
752  * the validity of user and group within the system is not checked.
753  * This is intentional since this way we can use id's which are not assigned.
754  */
755 bool chown(const std::string& path, const I2n::User& user, const I2n::Group& group)
756 {
757     uid_t uid= user.Uid;
758     if (uid<0) return false;
759     gid_t gid= group.Gid;
760     if (gid<0) gid= user.Gid;
761     if (gid<0) return false;
762     int res= ::chown( path.c_str(), uid, gid);
763     return (res==0);
764 } // eo chown(const std::string&,const User&,const Group&)
765
766 /**
767  * Recursive delete of files and directories
768  * @param path File or directory to delete
769  * @param keep_parent_dir Keep parent directory (=empty out directory) [optional]
770  * @param error Will contain the error if the return value is false [optional]
771  * @return true on success, false otherwise
772  */
773 bool recursive_delete(const std::string &path,
774                       bool keep_parent_dir,
775                       std::string *error)
776 {
777     bool rtn = true;
778
779     try
780     {
781         Stat sp(path, false);
782         if (!sp)
783             throw runtime_error("can't stat " + path);
784
785         if (sp.is_directory())
786         {
787             std::vector<std::string> dirents = get_dir(path, false);
788             BOOST_FOREACH(const std::string &filename, dirents)
789             {
790                 // Delete subdir or file.
791                 rtn = recursive_delete(path + "/" + filename, false, error);
792                 if (rtn == false)
793                     break;
794             }
795
796             if (keep_parent_dir == false && !rmdir(path))
797                 throw runtime_error("can't remove directory " + path);
798         }
799         else
800         {
801             if (!unlink(path))
802                 throw runtime_error("can't unlink " + path);
803         }
804     }
805     catch (exception &e)
806     {
807         if (error)
808         {
809             ostringstream out;
810             out << e.what() << " (" << strerror(errno) << ")";
811             *error = out.str();
812         }
813         rtn = false;
814     }
815     catch (...)
816     {
817         if (error)
818         {
819             ostringstream out;
820             out << "unknown error (" << strerror(errno) << ")";
821             *error = out.str();
822         }
823         rtn = false;
824     }
825
826     return rtn;
827 } // eo recursive_delete(const std::string&,std::string*)
828
829 /**
830     Create a unique temporary directory from path_template.
831     @param Path template. The last six characters must be XXXXXX.
832     @param error Will contain the error if the return value is empty [optional]
833     @return Name of new directory or empty string on error.
834
835     @seealso: classes in tmpfstream which offer functionality based on mkstemp
836 */
837 std::string mkdtemp(const std::string &path_template, std::string *error)
838 {
839     boost::scoped_array<char> buf( new char[path_template.size()+1] );
840     path_template.copy(buf.get(), path_template.size());
841     buf[path_template.size()]=0;
842
843     char *unique_dir = ::mkdtemp(buf.get());
844     if (!unique_dir)
845     {
846         if (error)
847             *error = strerror(errno);
848         return "";
849     }
850
851     // Scoped pointer is still valid
852     return std::string(unique_dir);
853 }
854
855 /**
856     Create directory
857     @param path Path to create
858     @param error Will contain the error if the return value is false [optional]
859     @return True on success, false on error
860 */
861 bool mkdir(const std::string &path, const mode_t &mode, std::string *error)
862 {
863     if ( ::mkdir(path.c_str(), mode) == 0)
864         return true;
865
866     if (error)
867         *error = strerror(errno);
868     return false;
869 }
870
871 /**
872     Remove directory
873     @param path Path to removed
874     @param error Will contain the error if the return value is false [optional]
875     @return True on successs, false otherwise
876 */
877 bool rmdir(const std::string &path, std::string *error)
878 {
879     if ( ::rmdir(path.c_str() ) == 0)
880         return true;
881
882     if (error)
883         *error = strerror(errno);
884     return false;
885 }
886
887 /// Small helper class for scoped free
888 class scoped_C_free
889 {
890 public:
891     scoped_C_free(void *ptr)
892         : pointer_to_free(ptr)
893     {
894     }
895
896     ~scoped_C_free()
897     {
898         free (pointer_to_free);
899         pointer_to_free = NULL;
900     }
901
902 private:
903     void *pointer_to_free;
904 };
905
906 /**
907     Get current working directory
908     @return Current working directory. Empty string on error.
909 */
910 std::string getcwd()
911 {
912     char *cwd = ::getcwd(NULL, 0);
913     if (!cwd)
914         return "";
915
916     // Make deallocation of cwd exception safe
917     scoped_C_free holder(cwd);
918
919     string current_dir(cwd);
920     return current_dir;
921 }
922
923 /**
924     Change current working directory
925     @param path Path to change to
926     @param error Will contain the error if the return value is false [optional]
927     @return True on successs, false otherwise
928 */
929 bool chdir(const std::string &path, std::string *error)
930 {
931     if ( ::chdir(path.c_str() ) == 0)
932         return true;
933
934     if (error)
935         *error = strerror(errno);
936     return false;
937 }
938
939 /**
940     Set file mode creation mask
941     @param mask Creation mask
942     @return Previous creation mask (function call always succeeds)
943 */
944 mode_t umask(mode_t mask)
945 {
946     return ::umask(mask);
947 }
948
949
950 /**
951  * @brief Remove unlisted files
952  *
953  * @param directory Directory to look for files
954  * @param keep_files List of files or directories to keep
955  * @param prefix Filename prefix to match. Empty prefix matches all.
956  *
957  * @return bool True if the directory was scanned, false on error (directory not found, permission denied)
958  **/
959 bool remove_unlisted_files(const std::string &directory,
960                            const std::set<std::string> &keep_files,
961                            const std::string &prefix)
962 {
963     std::vector<std::string> content;
964     if (!get_dir(directory, content, false))
965         return false;
966
967     bool all_fine = true;
968     BOOST_FOREACH(const std::string &file, content)
969     {
970         // Check for filename prefix (if any)
971         if (!prefix.empty() && file.find(prefix) != 0)
972             continue;
973
974         // Check if file is whitelisted
975         if (keep_files.find(file) != keep_files.end())
976             continue;
977
978         // Try to unlink file. (Continue on error)
979         if (!unlink(directory + "/" + file))
980             all_fine = false;
981     }
982
983     return all_fine;
984 }
985
986 /**
987  * @brief Get free size in bytes on a given path or filename
988  *
989  * @param path Directory or filename to look in
990  *
991  * @return Number of bytes available to a regular user, -1 in case of an error
992  **/
993 long long get_free_diskspace(const std::string& path)
994 {
995     struct statvfs sf;
996
997     int looplimit=10000;
998     int ret;
999     while ( ((ret=statvfs(path.c_str(),&sf)) == -1) && (errno==EINTR) && looplimit > 0)
1000         looplimit--;
1001
1002     if (ret==-1)
1003     {
1004         // a real error occured
1005         return -1;
1006     }
1007
1008     long long free_bytes=0;
1009
1010     // load block size
1011     free_bytes=sf.f_bsize;
1012
1013     // multiply by number of free blocks accessible by normal users
1014     // make sure we really multiply long long by long long and don't overflow at 2 GB
1015     free_bytes*=(long long)sf.f_bavail;
1016
1017     return free_bytes;
1018 }
1019
1020 namespace
1021 {
1022 // anonymous namespace to make du_internal inaccessible from outside
1023
1024 // internally used by du, do not use for other things
1025 void du_internal(const std::string &path, long long &sum, std::map<dev_t, std::set<ino_t> > &counted_inodes)
1026 {
1027
1028     Stat sp(path, false);      // don't dereference symlinks here
1029     if (!sp)
1030         throw runtime_error("can't stat " + path);
1031
1032     // make sure we don't count hardlinked files twice
1033     bool count_file=true;
1034
1035     // dirs can't be hardlinked, their nlink is the size of entries -> doesn't matter for us here
1036     if (!sp.is_directory() && sp.nlink() > 1)
1037     {
1038         // see if we have remembered this dev / inode combination
1039         if (counted_inodes[sp.device()].count(sp.inode()))
1040             count_file=false;
1041         else
1042             counted_inodes[sp.device()].insert(sp.inode());
1043     }
1044
1045     // always add the space used, even if we have a directory, symlink or whatever:
1046     // they need space on disk too
1047
1048     if (count_file)
1049         sum+=sp.bytes_on_disk();
1050
1051     if (sp.is_directory())
1052     {
1053         std::vector<std::string> dirents = get_dir(path, false);
1054         BOOST_FOREACH(const std::string &filename, dirents)
1055         {
1056             // calculate size of subdir or file
1057             du_internal(path + "/" + filename, sum, counted_inodes);
1058         }
1059     }
1060 }
1061
1062 } // eo anon namespace
1063
1064 /**
1065  * like du(1): return the number bytes used by a directory structure, counting hardlinked files only once
1066  * @param path File or directory to start counting recursively
1067  * @param error Will contain the error if the return value is -1 [optional]
1068  * @return size in bytes on success, -1 on error
1069  */
1070 long long du(const std::string &path, std::string *error)
1071 {
1072     long long sum = 0;
1073
1074     std::map<dev_t, std::set<ino_t> > counted_inodes;
1075
1076     try
1077     {
1078         du_internal(path, sum, counted_inodes);
1079     }
1080     catch (exception &e)
1081     {
1082         if (error)
1083         {
1084             ostringstream out;
1085             out << e.what() << " (" << strerror(errno) << ")";
1086             *error = out.str();
1087         }
1088         return -1;
1089     }
1090     catch (...)
1091     {
1092         if (error)
1093         {
1094             ostringstream out;
1095             out << "unknown error (" << strerror(errno) << ")";
1096             *error = out.str();
1097         }
1098         return -1;
1099     }
1100
1101     return sum;
1102 } // eo du
1103
1104
1105 } // eo namespace I2n