Rename get_dir_size() to get_dir_count()
[libi2ncommon] / src / filefunc.cpp
index 6f07011..e1e4882 100644 (file)
@@ -18,7 +18,7 @@ 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
@@ -40,6 +40,8 @@ on this file might be covered by the GNU General Public License.
 #include <unistd.h>
 #include <errno.h>
 #include <string.h>
+#include <map>
+#include <set>
 
 #include <boost/scoped_array.hpp>
 #include <boost/foreach.hpp>
@@ -306,6 +308,7 @@ bool get_dir(
     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)
     {
@@ -340,6 +343,32 @@ std::vector< std::string > get_dir(const std::string& path, bool include_dot_nam
 } // 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.
@@ -747,51 +776,46 @@ bool recursive_delete(const std::string &path,
 {
     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 store, *entry = NULL;
-            while (readdir_r(dir, &store, &entry) == 0 && entry != NULL)
+        if (sp.is_directory())
+        {
+            std::vector<std::string> dirents = get_dir(path, false);
+            BOOST_FOREACH(const std::string &filename, dirents)
             {
-                string filename = entry->d_name;
-                if (filename == "." || filename == "..") {
-                    continue;
-                }
-
                 // Delete subdir or file.
                 rtn = recursive_delete(path + "/" + filename, false, error);
-                if (rtn == false) {
+                if (rtn == false)
                     break;
-                }
             }
 
-            closedir(dir);
-            if (keep_parent_dir == false && !rmdir(path)) {
+            if (keep_parent_dir == false && !rmdir(path))
                 throw runtime_error("can't remove directory " + path);
-            }
-        } else {
-            if (!unlink(path)) {
+        }
+        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();
@@ -805,8 +829,10 @@ bool recursive_delete(const std::string &path,
 /**
     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 false [optional]
+    @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)
 {
@@ -968,8 +994,10 @@ 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) );
+    while ( ((ret=statvfs(path.c_str(),&sf)) == -1) && (errno==EINTR) && looplimit > 0)
+        looplimit--;
 
     if (ret==-1)
     {
@@ -989,5 +1017,89 @@ long long get_free_diskspace(const std::string& path)
     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