+/*
+The software in this package is distributed under the GNU General
+Public License version 2 (with a special exception described below).
+
+A copy of GNU General Public License (GPL) is included in this distribution,
+in the file COPYING.GPL.
+
+As a special exception, if other files instantiate templates or use macros
+or inline functions from this file, or you compile this file and link it
+with other works to produce a work based on this file, this file
+does not by itself cause the resulting work to be covered
+by the GNU General Public License.
+
+However the source code for this file must still be made available
+in accordance with section (3) of the GNU General Public License.
+
+This exception does not invalidate any other reasons why a work based
+on this file might be covered by the GNU General Public License.
+*/
/***************************************************************************
- escape.cpp - escaping of strings
+ filefunc.cpp - functions for working with FS
-------------------
begin : Sun Nov 14 1999
copyright : (C) 1999 by Intra2net AG
- email : info@intra2net.com
***************************************************************************/
#include <list>
#include <sys/types.h>
#include <sys/stat.h>
+#include <sys/statvfs.h>
#include <dirent.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include <errno.h>
+#include <string.h>
+#include <map>
+#include <set>
#include <boost/scoped_array.hpp>
+#include <boost/foreach.hpp>
#include "filefunc.hxx"
#include "stringfunc.hxx"
-
namespace I2n
{
*/
Stat::Stat()
-: Valid(false)
+ : FollowLinks(true)
+ , Valid(false)
{
clear();
} // eo Stat::Stat()
{
if (! Path.empty())
{
- stat(Path, FollowLinks);
+ // pass a copy of Path: otherwise clear() would leave an empty reference
+ stat(string(Path), FollowLinks);
}
} // eo Stat::recheck()
Mtime = stat_info.st_mtime;
Ctime = stat_info.st_atime;
+ // the stat(2) manpage for linux defines that st_blocks is given in a number of 512-byte-blocks.
+ BytesOnDisk = stat_info.st_blocks;
+ BytesOnDisk*=(long long)512;
+
IsLink= S_ISLNK( stat_info.st_mode );
IsRegular= S_ISREG( stat_info.st_mode );
IsDirectory= S_ISDIR( stat_info.st_mode );
Gid = 0;
DeviceType = 0;
Size = 0;
+ BytesOnDisk = 0;
Atime = 0;
Mtime = 0;
Ctime = 0;
/**
+ * @brief Check if two files differ
+ *
+ * Note: Reads the whole file into memory
+ * if the file size is identical.
+ *
+ * @param old_filename Filename of old file
+ * @param new_filename Filename of new file
+ *
+ * @return bool True if files differ, false otherwise.
+ * If one file does not exist, also returns true
+ */
+bool file_content_differs(const std::string &old_filename, const std::string &new_filename)
+{
+ if (I2n::file_exists(old_filename) == false ||
+ I2n::file_exists(new_filename) == false)
+ return true;
+
+ // check if size differs
+ if (I2n::file_size(old_filename) != I2n::file_size(new_filename))
+ return true;
+
+ const std::string old_content = I2n::read_file(old_filename);
+ const std::string new_content = I2n::read_file(new_filename);
+
+ // check if content differs
+ if (old_content == new_content)
+ return false;
+
+ // Differ by default (fallback)
+ return true;
+}
+
+
+/**
* @brief reads the contents of a directory.
* @param path the path to the directory whose contents should be read.
* @param[out] result the resulting list of names.
std::vector< std::string >& result,
bool include_dot_names )
{
+ // code copied to get_dir_count; keep in sync
DIR* dir = ::opendir( path.c_str());
if (!dir)
{
return false;
}
- struct dirent *entry;
- while ( NULL != (entry = ::readdir(dir)) )
+ struct dirent store, *entry = NULL;
+ while (readdir_r(dir, &store, &entry) == 0 && entry != NULL)
{
std::string name( entry->d_name );
if (! include_dot_names && (name[0] == '.') )
} // eo get_dir(const std::string&,bool)
+/**
+ * @brief count entries in directory, like get_dir(path, include_dot_names).size()
+ * @param path the path to the directory whose contents should be read.
+ * @param include_dot_names determines if dot-files should be included in the count.
+ * @return the number of entries in the directory; return -1 in case of error
+ */
+int get_dir_count(const std::string& path, bool include_dot_names)
+{
+ // code is a simplified copy of get_dir, above. Keep in sync
+ int result = 0;
+ DIR* dir = ::opendir( path.c_str());
+ if (!dir)
+ return -1;
+ struct dirent store, *entry = NULL;
+ while (readdir_r(dir, &store, &entry) == 0 && entry != NULL)
+ {
+ if (entry->d_name == NULL)
+ continue; // should not happen
+ else if (! include_dot_names && (entry->d_name)[0] == '.')
+ continue;
+ ++result;
+ }
+ ::closedir(dir);
+ return result;
+}
+
/**
* @brief removes a file from a filesystem.
* @param path path to the file.
- * @return @a true iff the unlink was successful.
+ * @return @a true if the unlink was successful.
*/
bool unlink( const std::string& path )
{
* @brief writes a string to a file.
* @param path path to the file
* @param data the data which should be written into the file
+ * @param trunc set the trunc flag when opening the file. Do not use for files in /proc and /sys
* @return @a true if the data was written to the file.
*
* A simple (q'n'd) function for writing a string to a file.
*/
-bool write_file(const std::string& path, const std::string& data)
+bool write_file(const std::string& path, const std::string& data, bool trunc)
{
- std::ofstream f( path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
+ // set the correct openmode flags
+ std::ios_base::openmode flags = std::ios::out | std::ios::binary;
+ if (trunc)
+ flags |= std::ios::trunc;
+
+ std::ofstream f( path.c_str(), flags);
if (f.good())
{
f.write( data.data(), data.size() );
/**
+ * Copy file in 4k blocks from source to target.
+ * Overwrites the target if it already exists.
+ *
+ * On error the target file gets removed.
+ *
+ * @param src source file
+ * @param dest target file
+ * @return true if all is ok, false on error
+ */
+bool copy_file(const std::string& src, const std::string& dest)
+{
+ std::ifstream input( src.c_str(), std::ios::in | std::ios::binary );
+ if (!input)
+ return false;
+
+ std::ofstream output( dest.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
+ if (!output)
+ return false;
+
+ // Out of disc space?
+ if (!copy_stream(input,output))
+ {
+ output.close();
+ unlink(dest);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Copy streams in 4k blocks.
+ *
+ * @param is source stream
+ * @param os target stream
+ * @return true if all is ok, false on error
+ */
+bool copy_stream(std::istream& is, std::ostream& os)
+{
+ if (!is)
+ return false;
+
+ if (!os)
+ return false;
+
+ char buffer[4096];
+ while (is.good())
+ {
+ is.read(buffer, sizeof(buffer));
+ os.write(buffer, is.gcount());
+
+ // Can't write?
+ if (!os.good())
+ return false;
+ }
+
+ return true;
+}
+
+/**
* @brief returns the filename part of a path (last component)
* @param path the path.
* @return the last component of the path.
/**
+ * @brief calls fsync on a given directory to sync all it's metadata
+ * @param path the path of the directory.
+ * @return true if successful
+ */
+bool dirsync(const std::string& path)
+{
+ // sync the directory the file is in
+ DIR* dir=opendir(path.c_str());
+ if (dir == NULL)
+ return false;
+
+ int ret=fsync(dirfd(dir));
+
+ closedir(dir);
+
+ return (ret==0);
+}
+
+/**
* @brief changes the file(/path) mode.
* @param path the path to change the mode for.
* @param mode the new file mode.
/**
* Recursive delete of files and directories
* @param path File or directory to delete
- * @param error Will contain the error if the return value is false
+ * @param keep_parent_dir Keep parent directory (=empty out directory) [optional]
+ * @param error Will contain the error if the return value is false [optional]
* @return true on success, false otherwise
*/
-bool recursive_delete(const std::string &path, std::string *error)
+bool recursive_delete(const std::string &path,
+ bool keep_parent_dir,
+ std::string *error)
{
bool rtn = true;
- try {
- struct stat my_stat;
- if (stat(path.c_str(), &my_stat) != 0) {
+ try
+ {
+ Stat sp(path, false);
+ if (!sp)
throw runtime_error("can't stat " + path);
- }
-
- if (S_ISDIR(my_stat.st_mode)) {
- DIR *dir = opendir(path.c_str());
- if (!dir) {
- throw runtime_error("can't open directory " + path);
- }
-
- struct dirent *entry;
- while ((entry = readdir(dir))) {
- string filename = entry->d_name;
- if (filename == "." || filename == "..") {
- continue;
- }
+ if (sp.is_directory())
+ {
+ std::vector<std::string> dirents = get_dir(path, false);
+ BOOST_FOREACH(const std::string &filename, dirents)
+ {
// Delete subdir or file.
- rtn = recursive_delete(path + "/" + filename, error);
- if (rtn == false) {
+ rtn = recursive_delete(path + "/" + filename, false, error);
+ if (rtn == false)
break;
- }
}
- closedir(dir);
- if (rmdir(path.c_str()) != 0) {
+ if (keep_parent_dir == false && !rmdir(path))
throw runtime_error("can't remove directory " + path);
- }
- } else {
- if (unlink(path.c_str()) != 0) {
+ }
+ else
+ {
+ if (!unlink(path))
throw runtime_error("can't unlink " + path);
- }
}
- } catch (exception &e) {
- if (error) {
+ }
+ catch (exception &e)
+ {
+ if (error)
+ {
ostringstream out;
out << e.what() << " (" << strerror(errno) << ")";
*error = out.str();
}
rtn = false;
- } catch (...) {
- if (error) {
+ }
+ catch (...)
+ {
+ if (error)
+ {
ostringstream out;
out << "unknown error (" << strerror(errno) << ")";
*error = out.str();
return rtn;
} // eo recursive_delete(const std::string&,std::string*)
+/**
+ Create a unique temporary directory from path_template.
+ @param Path template. The last six characters must be XXXXXX.
+ @param error Will contain the error if the return value is empty [optional]
+ @return Name of new directory or empty string on error.
+
+ @seealso: classes in tmpfstream which offer functionality based on mkstemp
+*/
+std::string mkdtemp(const std::string &path_template, std::string *error)
+{
+ boost::scoped_array<char> buf( new char[path_template.size()+1] );
+ path_template.copy(buf.get(), path_template.size());
+ buf[path_template.size()]=0;
+
+ char *unique_dir = ::mkdtemp(buf.get());
+ if (!unique_dir)
+ {
+ if (error)
+ *error = strerror(errno);
+ return "";
+ }
+
+ // Scoped pointer is still valid
+ return std::string(unique_dir);
+}
+
+/**
+ Create directory
+ @param path Path to create
+ @param error Will contain the error if the return value is false [optional]
+ @return True on success, false on error
+*/
+bool mkdir(const std::string &path, const mode_t &mode, std::string *error)
+{
+ if ( ::mkdir(path.c_str(), mode) == 0)
+ return true;
+
+ if (error)
+ *error = strerror(errno);
+ return false;
+}
+
+/**
+ Remove directory
+ @param path Path to removed
+ @param error Will contain the error if the return value is false [optional]
+ @return True on successs, false otherwise
+*/
+bool rmdir(const std::string &path, std::string *error)
+{
+ if ( ::rmdir(path.c_str() ) == 0)
+ return true;
+
+ if (error)
+ *error = strerror(errno);
+ return false;
+}
+
+/// Small helper class for scoped free
+class scoped_C_free
+{
+public:
+ scoped_C_free(void *ptr)
+ : pointer_to_free(ptr)
+ {
+ }
+
+ ~scoped_C_free()
+ {
+ free (pointer_to_free);
+ pointer_to_free = NULL;
+ }
+
+private:
+ void *pointer_to_free;
+};
+
+/**
+ Get current working directory
+ @return Current working directory. Empty string on error.
+*/
+std::string getcwd()
+{
+ char *cwd = ::getcwd(NULL, 0);
+ if (!cwd)
+ return "";
+
+ // Make deallocation of cwd exception safe
+ scoped_C_free holder(cwd);
+
+ string current_dir(cwd);
+ return current_dir;
+}
+
+/**
+ Change current working directory
+ @param path Path to change to
+ @param error Will contain the error if the return value is false [optional]
+ @return True on successs, false otherwise
+*/
+bool chdir(const std::string &path, std::string *error)
+{
+ if ( ::chdir(path.c_str() ) == 0)
+ return true;
+
+ if (error)
+ *error = strerror(errno);
+ return false;
+}
+
+/**
+ Set file mode creation mask
+ @param mask Creation mask
+ @return Previous creation mask (function call always succeeds)
+*/
+mode_t umask(mode_t mask)
+{
+ return ::umask(mask);
+}
+
+
+/**
+ * @brief Remove unlisted files
+ *
+ * @param directory Directory to look for files
+ * @param keep_files List of files or directories to keep
+ * @param prefix Filename prefix to match. Empty prefix matches all.
+ *
+ * @return bool True if the directory was scanned, false on error (directory not found, permission denied)
+ **/
+bool remove_unlisted_files(const std::string &directory,
+ const std::set<std::string> &keep_files,
+ const std::string &prefix)
+{
+ std::vector<std::string> content;
+ if (!get_dir(directory, content, false))
+ return false;
+
+ bool all_fine = true;
+ BOOST_FOREACH(const std::string &file, content)
+ {
+ // Check for filename prefix (if any)
+ if (!prefix.empty() && file.find(prefix) != 0)
+ continue;
+
+ // Check if file is whitelisted
+ if (keep_files.find(file) != keep_files.end())
+ continue;
+
+ // Try to unlink file. (Continue on error)
+ if (!unlink(directory + "/" + file))
+ all_fine = false;
+ }
+
+ return all_fine;
+}
+
+/**
+ * @brief Get free size in bytes on a given path or filename
+ *
+ * @param path Directory or filename to look in
+ *
+ * @return Number of bytes available to a regular user, -1 in case of an error
+ **/
+long long get_free_diskspace(const std::string& path)
+{
+ struct statvfs sf;
+
+ int looplimit=10000;
+ int ret;
+ while ( ((ret=statvfs(path.c_str(),&sf)) == -1) && (errno==EINTR) && looplimit > 0)
+ looplimit--;
+
+ if (ret==-1)
+ {
+ // a real error occured
+ return -1;
+ }
+
+ long long free_bytes=0;
+
+ // load block size
+ free_bytes=sf.f_bsize;
+
+ // multiply by number of free blocks accessible by normal users
+ // make sure we really multiply long long by long long and don't overflow at 2 GB
+ free_bytes*=(long long)sf.f_bavail;
+
+ return free_bytes;
+}
+
+namespace
+{
+// anonymous namespace to make du_internal inaccessible from outside
+
+// internally used by du, do not use for other things
+void du_internal(const std::string &path, long long &sum, std::map<dev_t, std::set<ino_t> > &counted_inodes)
+{
+
+ Stat sp(path, false); // don't dereference symlinks here
+ if (!sp)
+ throw runtime_error("can't stat " + path);
+
+ // make sure we don't count hardlinked files twice
+ bool count_file=true;
+
+ // dirs can't be hardlinked, their nlink is the size of entries -> doesn't matter for us here
+ if (!sp.is_directory() && sp.nlink() > 1)
+ {
+ // see if we have remembered this dev / inode combination
+ if (counted_inodes[sp.device()].count(sp.inode()))
+ count_file=false;
+ else
+ counted_inodes[sp.device()].insert(sp.inode());
+ }
+
+ // always add the space used, even if we have a directory, symlink or whatever:
+ // they need space on disk too
+
+ if (count_file)
+ sum+=sp.bytes_on_disk();
+
+ if (sp.is_directory())
+ {
+ std::vector<std::string> dirents = get_dir(path, false);
+ BOOST_FOREACH(const std::string &filename, dirents)
+ {
+ // calculate size of subdir or file
+ du_internal(path + "/" + filename, sum, counted_inodes);
+ }
+ }
+}
+
+} // eo anon namespace
+
+/**
+ * like du(1): return the number bytes used by a directory structure, counting hardlinked files only once
+ * @param path File or directory to start counting recursively
+ * @param error Will contain the error if the return value is -1 [optional]
+ * @return size in bytes on success, -1 on error
+ */
+long long du(const std::string &path, std::string *error)
+{
+ long long sum = 0;
+
+ std::map<dev_t, std::set<ino_t> > counted_inodes;
+
+ try
+ {
+ du_internal(path, sum, counted_inodes);
+ }
+ catch (exception &e)
+ {
+ if (error)
+ {
+ ostringstream out;
+ out << e.what() << " (" << strerror(errno) << ")";
+ *error = out.str();
+ }
+ return -1;
+ }
+ catch (...)
+ {
+ if (error)
+ {
+ ostringstream out;
+ out << "unknown error (" << strerror(errno) << ")";
+ *error = out.str();
+ }
+ return -1;
+ }
+
+ return sum;
+} // eo du
+
+
} // eo namespace I2n