2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
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.
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.
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.
20 /***************************************************************************
21 filefunc.cpp - functions for working with FS
23 begin : Sun Nov 14 1999
24 copyright : (C) 1999 by Intra2net AG
25 ***************************************************************************/
34 #include <sys/types.h>
36 #include <sys/statvfs.h>
46 #include <boost/scoped_array.hpp>
47 #include <boost/foreach.hpp>
48 #include "filefunc.hxx"
49 #include "stringfunc.hxx"
57 ** implementation of Stat
68 Stat::Stat(const std::string& path, bool follow_links)
70 stat(path,follow_links);
71 } // eo Stat::Stat(const std::string&,bool)
80 * @brief updates the internal data.
82 * In other words: stat()'s the file again.
88 // pass a copy of Path: otherwise clear() would leave an empty reference
89 stat(string(Path), FollowLinks);
91 } // eo Stat::recheck()
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.
100 void Stat::stat(const std::string& path, bool follow_links)
104 FollowLinks= follow_links;
105 struct stat stat_info;
107 res = ( follow_links ? ::stat(path.c_str(), &stat_info) : ::lstat(path.c_str(), &stat_info) );
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;
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;
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 );
135 } // eo Stat::stat(const std::string&,bool)
139 * @brief clears the internal data.
162 IsCharacterDevice= false;
163 IsBlockDevice= false;
166 } // eo Stat::clear()
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.
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)!
177 bool Stat::is_same_as(const Stat& rhs)
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);
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.
190 * "Same device" means that the devices have the same type and the same major and minor id.
192 bool Stat::is_same_device_as(const Stat& rhs)
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&)
201 * @brief check existence of a path.
202 * @param path path which should be tested.
203 * @return @a true iff path exists.
205 bool path_exists(const std::string& path)
207 struct stat stat_info;
208 int res = ::stat(path.c_str(), &stat_info);
209 if (res) return false;
211 } // eo path_exists(const std::string&)
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.
220 bool file_exists(const std::string& path)
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&)
228 // TODO: Use Stat class
231 * @param name filename to get size for
232 * @return file size or -1 if file does not exist
234 long file_size (const string &name)
238 struct stat statbuff;
240 if (lstat(name.c_str(), &statbuff) < 0)
243 if (!S_ISREG(statbuff.st_mode))
246 iReturn=statbuff.st_size;
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.
256 time_t file_mtime(const std::string& path)
258 struct stat stat_info;
259 int res = ::stat(path.c_str(), &stat_info);
261 return stat_info.st_mtime;
262 } // eo file_mtime(const std::string&)
266 * @brief Check if two files differ
268 * Note: Reads the whole file into memory
269 * if the file size is identical.
271 * @param old_filename Filename of old file
272 * @param new_filename Filename of new file
274 * @return bool True if files differ, false otherwise.
275 * If one file does not exist, also returns true
277 bool file_content_differs(const std::string &old_filename, const std::string &new_filename)
279 if (I2n::file_exists(old_filename) == false ||
280 I2n::file_exists(new_filename) == false)
283 // check if size differs
284 if (I2n::file_size(old_filename) != I2n::file_size(new_filename))
287 const std::string old_content = I2n::read_file(old_filename);
288 const std::string new_content = I2n::read_file(new_filename);
290 // check if content differs
291 if (old_content == new_content)
294 // Differ by default (fallback)
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.
307 const std::string& path,
308 std::vector< std::string >& result,
309 bool include_dot_names )
311 // code copied to get_dir_count; keep in sync
312 DIR* dir = ::opendir( path.c_str());
317 struct dirent store, *entry = NULL;
318 while (readdir_r(dir, &store, &entry) == 0 && entry != NULL)
320 std::string name( entry->d_name );
321 if (! include_dot_names && (name[0] == '.') )
325 result.push_back( name );
329 } // eo get_dir(const std::string&,std::vector< std::string >&,bool)
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).
338 std::vector< std::string > get_dir(const std::string& path, bool include_dot_names )
340 std::vector< std::string > result;
341 get_dir(path,result,include_dot_names);
343 } // eo get_dir(const std::string&,bool)
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
352 int get_dir_count(const std::string& path, bool include_dot_names)
354 // code is a simplified copy of get_dir, above. Keep in sync
356 DIR* dir = ::opendir( path.c_str());
359 struct dirent store, *entry = NULL;
360 while (readdir_r(dir, &store, &entry) == 0 && entry != NULL)
362 if (entry->d_name == NULL)
363 continue; // should not happen
364 else if (! include_dot_names && (entry->d_name)[0] == '.')
374 * @brief removes a file from a filesystem.
375 * @param path path to the file.
376 * @return @a true if the unlink was successful.
378 bool unlink( const std::string& path )
380 int res = ::unlink( path.c_str() );
382 } // eo unlink(const std::string&)
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.
393 bool symlink(const std::string& target, const std::string& link_name, bool force)
396 if (target.empty() or link_name.empty() or target == link_name)
398 // no, we don't do this!
401 std::string n_target;
402 if (target[0] == '/') // absolute target?
406 else // relative target
408 // for stat'ing: prepend dir of link_name:
409 n_target= dirname(link_name)+"/"+ target;
411 Stat target_stat(n_target, false);
412 Stat link_name_stat(link_name, false);
413 if (target_stat.exists() && link_name_stat.exists())
415 if (link_name_stat.is_same_as(target_stat)
416 or link_name_stat.is_same_device_as(target_stat) )
420 //TODO: more consistency checks?!
422 if (link_name_stat.exists())
424 // the link name already exists.
427 // "force" as given, so try to remove the link_name:
430 link_name_stat.recheck();
433 if (link_name_stat.exists())
435 // well, if the link_name still exists; we cannot create that link:
439 res= ::symlink(target.c_str(), link_name.c_str());
441 } // eo symlink(const std::string&,const std::string&,bool)
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.
450 std::string read_link(const std::string& path)
453 Stat stat(path,false);
454 if (!stat || !stat.is_link())
456 return std::string();
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 );
463 return std::string( buffer_ptr.get(), res );
465 return std::string();
466 } // eo read_link(const std::string&)
471 * @brief returns content of a file as string.
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).
477 * @param path path to the file.
478 * @return the content of the file as string (empty if file could be opened).
480 std::string read_file(const std::string& path)
485 return std::string();
487 std::ifstream f( path.c_str(), std::ios::in | std::ios::binary );
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!
496 // if we know the size, we reserve enough space.
497 result.reserve( stat.size() );
502 f.read(buffer, sizeof(buffer));
503 result.append(buffer, f.gcount());
507 } // eo read_file(const std::string&)
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.
517 * A simple (q'n'd) function for writing a string to a file.
519 bool write_file(const std::string& path, const std::string& data, bool trunc)
521 // set the correct openmode flags
522 std::ios_base::openmode flags = std::ios::out | std::ios::binary;
524 flags |= std::ios::trunc;
526 std::ofstream f( path.c_str(), flags);
529 f.write( data.data(), data.size() );
536 } // eo write_file(const std::string&,const std::string&)
540 * Copy file in 4k blocks from source to target.
541 * Overwrites the target if it already exists.
543 * On error the target file gets removed.
545 * @param src source file
546 * @param dest target file
547 * @return true if all is ok, false on error
549 bool copy_file(const std::string& src, const std::string& dest)
551 std::ifstream input( src.c_str(), std::ios::in | std::ios::binary );
555 std::ofstream output( dest.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
559 // Out of disc space?
560 if (!copy_stream(input,output))
571 * Copy streams in 4k blocks.
573 * @param is source stream
574 * @param os target stream
575 * @return true if all is ok, false on error
577 bool copy_stream(std::istream& is, std::ostream& os)
588 is.read(buffer, sizeof(buffer));
589 os.write(buffer, is.gcount());
600 * @brief returns the filename part of a path (last component)
601 * @param path the path.
602 * @return the last component of the path.
604 std::string basename(const std::string& path)
606 std::string::size_type pos= path.rfind('/');
607 if (pos != std::string::npos)
609 return path.substr(pos+1);
612 } // eo basename(const std::string&)
616 * @brief returns the directory part of a path.
617 * @param path the path.
618 * @return the directory part of the path.
620 std::string dirname(const std::string& path)
622 std::string::size_type pos= path.rfind('/');
623 if (pos != std::string::npos)
625 std::string result(path,0,pos);
633 } // eo dirname(const std::string&)
637 * @brief normalizes a path.
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.
644 * @param path the path which should be normalized.
645 * @return the normalized path.
648 std::string normalize_path(const std::string& path)
652 return std::string();
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;
660 split_string(path,parts,"/",true);
662 for(std::list< std::string >::const_iterator it_parts= parts.begin();
663 it_parts != parts.end();
666 std::string part(*it_parts); //convenience..
667 if (part == std::string(".") )
669 // single dot is "current path"; ignore!
672 if (part == std::string("..") )
674 // double dot is "one part back"
675 if (result_parts.empty())
679 // ignore since we cannot move behind / on absolute paths...
683 // on relative path, we need to store the "..":
684 result_parts.push_back(part);
687 else if (result_parts.back() == std::string("..") )
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);
695 // remove last element.
696 result_parts.pop_back();
700 result_parts.push_back(part);
707 result+= join_string(result_parts,"/");
709 } // eo normalize_path(const std::string&)
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
717 bool dirsync(const std::string& path)
719 // sync the directory the file is in
720 DIR* dir=opendir(path.c_str());
724 int ret=fsync(dirfd(dir));
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.
737 bool chmod(const std::string& path, int mode)
739 int res= ::chmod(path.c_str(), mode);
741 } // eo chmod(const std::string&,int)
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.
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.
755 bool chown(const std::string& path, const I2n::User& user, const I2n::Group& group)
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);
764 } // eo chown(const std::string&,const User&,const Group&)
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
773 bool recursive_delete(const std::string &path,
774 bool keep_parent_dir,
781 Stat sp(path, false);
783 throw runtime_error("can't stat " + path);
785 if (sp.is_directory())
787 std::vector<std::string> dirents = get_dir(path, false);
788 BOOST_FOREACH(const std::string &filename, dirents)
790 // Delete subdir or file.
791 rtn = recursive_delete(path + "/" + filename, false, error);
796 if (keep_parent_dir == false && !rmdir(path))
797 throw runtime_error("can't remove directory " + path);
802 throw runtime_error("can't unlink " + path);
810 out << e.what() << " (" << strerror(errno) << ")";
820 out << "unknown error (" << strerror(errno) << ")";
827 } // eo recursive_delete(const std::string&,std::string*)
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.
835 @seealso: classes in tmpfstream which offer functionality based on mkstemp
837 std::string mkdtemp(const std::string &path_template, std::string *error)
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;
843 char *unique_dir = ::mkdtemp(buf.get());
847 *error = strerror(errno);
851 // Scoped pointer is still valid
852 return std::string(unique_dir);
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
861 bool mkdir(const std::string &path, const mode_t &mode, std::string *error)
863 if ( ::mkdir(path.c_str(), mode) == 0)
867 *error = strerror(errno);
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
877 bool rmdir(const std::string &path, std::string *error)
879 if ( ::rmdir(path.c_str() ) == 0)
883 *error = strerror(errno);
887 /// Small helper class for scoped free
891 scoped_C_free(void *ptr)
892 : pointer_to_free(ptr)
898 free (pointer_to_free);
899 pointer_to_free = NULL;
903 void *pointer_to_free;
907 Get current working directory
908 @return Current working directory. Empty string on error.
912 char *cwd = ::getcwd(NULL, 0);
916 // Make deallocation of cwd exception safe
917 scoped_C_free holder(cwd);
919 string current_dir(cwd);
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
929 bool chdir(const std::string &path, std::string *error)
931 if ( ::chdir(path.c_str() ) == 0)
935 *error = strerror(errno);
940 Set file mode creation mask
941 @param mask Creation mask
942 @return Previous creation mask (function call always succeeds)
944 mode_t umask(mode_t mask)
946 return ::umask(mask);
951 * @brief Remove unlisted files
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.
957 * @return bool True if the directory was scanned, false on error (directory not found, permission denied)
959 bool remove_unlisted_files(const std::string &directory,
960 const std::set<std::string> &keep_files,
961 const std::string &prefix)
963 std::vector<std::string> content;
964 if (!get_dir(directory, content, false))
967 bool all_fine = true;
968 BOOST_FOREACH(const std::string &file, content)
970 // Check for filename prefix (if any)
971 if (!prefix.empty() && file.find(prefix) != 0)
974 // Check if file is whitelisted
975 if (keep_files.find(file) != keep_files.end())
978 // Try to unlink file. (Continue on error)
979 if (!unlink(directory + "/" + file))
987 * @brief Get free size in bytes on a given path or filename
989 * @param path Directory or filename to look in
991 * @return Number of bytes available to a regular user, -1 in case of an error
993 long long get_free_diskspace(const std::string& path)
999 while ( ((ret=statvfs(path.c_str(),&sf)) == -1) && (errno==EINTR) && looplimit > 0)
1004 // a real error occured
1008 long long free_bytes=0;
1011 free_bytes=sf.f_bsize;
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;
1022 // anonymous namespace to make du_internal inaccessible from outside
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)
1028 Stat sp(path, false); // don't dereference symlinks here
1030 throw runtime_error("can't stat " + path);
1032 // make sure we don't count hardlinked files twice
1033 bool count_file=true;
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)
1038 // see if we have remembered this dev / inode combination
1039 if (counted_inodes[sp.device()].count(sp.inode()))
1042 counted_inodes[sp.device()].insert(sp.inode());
1045 // always add the space used, even if we have a directory, symlink or whatever:
1046 // they need space on disk too
1049 sum+=sp.bytes_on_disk();
1051 if (sp.is_directory())
1053 std::vector<std::string> dirents = get_dir(path, false);
1054 BOOST_FOREACH(const std::string &filename, dirents)
1056 // calculate size of subdir or file
1057 du_internal(path + "/" + filename, sum, counted_inodes);
1062 } // eo anon namespace
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
1070 long long du(const std::string &path, std::string *error)
1074 std::map<dev_t, std::set<ino_t> > counted_inodes;
1078 du_internal(path, sum, counted_inodes);
1080 catch (exception &e)
1085 out << e.what() << " (" << strerror(errno) << ")";
1095 out << "unknown error (" << strerror(errno) << ")";
1105 } // eo namespace I2n