libi2ncommon: (tomj) merged common code from connd
authorThomas Jarosch <thomas.jarosch@intra2net.com>
Fri, 4 Apr 2008 15:46:45 +0000 (15:46 +0000)
committerThomas Jarosch <thomas.jarosch@intra2net.com>
Fri, 4 Apr 2008 15:46:45 +0000 (15:46 +0000)
src/Makefile.am
src/daemonfunc.cpp
src/daemonfunc.hxx
src/filefunc.cpp
src/filefunc.hxx
src/stringfunc.cpp
src/stringfunc.hxx

index 421cca4..1ddd7c8 100644 (file)
@@ -4,8 +4,8 @@ INCLUDES = -I$(top_srcdir)/src @LIBGETTEXT_CFLAGS@ @LIBICONV_CFLAGS@ $(all_inclu
 
 # the library search path.
 lib_LTLIBRARIES = libi2ncommon.la
-include_HEADERS = daemonfunc.hxx pidfile.hxx logread.hxx insocketstream.hxx oftmpstream.hxx pipestream.hxx filefunc.hxx stringfunc.hxx timefunc.hxx ipfunc.hxx ip_type.hxx
-libi2ncommon_la_SOURCES = daemonfunc.cpp pidfile.cpp logread.cpp oftmpstream.cpp ipfunc.cpp timefunc.cpp filefunc.cpp stringfunc.cpp
+include_HEADERS = userfunc.hxx daemonfunc.hxx pidfile.hxx logread.hxx insocketstream.hxx oftmpstream.hxx pipestream.hxx filefunc.hxx stringfunc.hxx timefunc.hxx ipfunc.hxx ip_type.hxx
+libi2ncommon_la_SOURCES = userfunc.cpp daemonfunc.cpp pidfile.cpp logread.cpp oftmpstream.cpp ipfunc.cpp timefunc.cpp filefunc.cpp stringfunc.cpp
 
 # Note:  If you specify a:b:c as the version in the next line,
 #  the library that is made has version (a-c).c.b.  In this
index b1040b2..274c6a5 100644 (file)
 #include <string>
 #include <stdexcept>
 #include "daemonfunc.hxx"
+#include "stringfunc.hxx"
+#include "filefunc.hxx"
+
+namespace i2n
+{
+namespace daemon
+{
 
 using namespace std;
 
 /**
  * Fork into the background.
  */
-void daemon::daemonize()
+void daemonize()
 {
     int pid=fork();
 
@@ -34,43 +41,14 @@ void daemon::daemonize()
 }
 
 /**
- * Lookup uid for given username
- * @param username username to convert
- * @return uid of the user
- */
-uid_t daemon::lookup_uid(const std::string &username)
-{
-    struct passwd *user = getpwnam(username.c_str());
-    if (user == NULL) {
-        throw runtime_error("user " + username + " not found");
-    }
-
-    return user->pw_uid;
-}
-
-/**
- * Lookup gid for given group
- * @param group group to convert
- * @return gid of the group
- */
-gid_t daemon::lookup_gid(const std::string &group)
-{
-    struct group *my_group = getgrnam(group.c_str());
-    if (my_group == NULL) {
-        throw runtime_error("group " + group + " not found");
-    }
-
-    return my_group->gr_gid;
-}
-
-/**
  * Drop root privileges
  * @param username User to become. Don't change user if empty
  * @param group Group to become. Don't change group if empty
  */
-void daemon::drop_root_privileges(const std::string &username,
+void drop_root_privileges(const std::string &username,
                                                  const std::string &group)
 {
+/*
     if (!group.empty()) {
         if (setgid(daemon::lookup_gid(group))) {
             throw runtime_error("Can't change to group " + group);
@@ -82,4 +60,67 @@ void daemon::drop_root_privileges(const std::string &username,
             throw runtime_error("Can't change to user " + username);
         }
     }
+*/
+}
+
+/**
+ * @brief determine the pids for a given program
+ * @param[in] name name (or full path) of the binary
+ * @param[out] result the pids associated with the name.
+ * @return @a true if the function performed without errors.
+ *
+ * Walk though the /proc/\<pid\>'s and search for the name.
+ *
+ * @note Since this function uses /proc, it's system specific. Currently:
+ * Linux only!
+ *
+ * @todo check cmdline and stat in /proc/\<pid\> dir for the searched name.
+ */
+bool pid_of(const std::string& name, std::vector< pid_t >& result)
+{
+    std::vector< std::string > entries;
+    std::vector< pid_t > fuzz1_result;
+    std::vector< pid_t > fuzz2_result;
+    result.clear();
+    if (!i2n::getDir("/proc", entries)) return false;
+    for(std::vector< std::string >::const_iterator it= entries.begin();
+        it != entries.end();
+        ++it)
+    {
+        pid_t pid;
+        if (! stringTo<pid_t>(*it, pid)) continue;
+        std::string base_path= std::string("/proc/") + *it;
+        std::string exe_path= base_path + "/exe";
+        Stat stat(exe_path, false);
+        if (not stat or not stat.isLink()) continue;
+        std::string real_exe= readLink(exe_path);
+        if (real_exe == name)
+        {
+            result.push_back( pid );
+            continue;
+        }
+
+        std::string proc_stat= readFile( base_path + "/stat");
+        if (proc_stat.empty()) continue; // process vanished
+
+        //TODO some more fuzz tests here?! (cmdline, stat(us))
+
+        if (basename(real_exe) == name)
+        {
+            fuzz2_result.push_back(pid);
+            continue;
+        }
+    }
+    if (result.empty())
+    {
+        result.swap(fuzz1_result);
+    }
+    if (result.empty())
+    {
+        result.swap(fuzz2_result);
+    }
+    return true;
+} // eo pidOf(const std::string&,std::vector< pid_t >&)
+
+}
 }
index ba26433..a0e8011 100644 (file)
@@ -8,18 +8,20 @@
 
 #include <sys/types.h>
 #include <string>
+#include <vector>
 
 /// Collection of functions for daemons
-class daemon
+namespace i2n
 {
-public:
-    static void daemonize();
+namespace daemon
+{
+    void daemonize();
 
-    static uid_t lookup_uid(const std::string &username);
-    static gid_t lookup_gid(const std::string &group);
+    void drop_root_privileges(const std::string &username,
+                                              const std::string &group);
 
-    static void drop_root_privileges(const std::string &username,
-                                                          const std::string &group);
-};
+    bool pid_of(const std::string& name, std::vector< pid_t >& result);
+}
+}
 
 #endif
index f65db18..47e9838 100644 (file)
@@ -6,7 +6,9 @@
     email                : info@intra2net.com
  ***************************************************************************/
 
+#include <list>
 #include <string>
+#include <fstream>
 #include <sstream>
 #include <iostream>
 #include <stdexcept>
 #include <unistd.h>
 #include <errno.h>
 
-#include <filefunc.hxx>
+#include <boost/scoped_array.hpp>
+#include "filefunc.hxx"
+#include "stringfunc.hxx"
+
+namespace i2n
+{
 
 using namespace std;
 
-bool file_exists (const string &name)
+/*
+** implementation of Stat
+*/
+
+Stat::Stat()
+: m_valid(false)
 {
-    struct stat statbuff;
+    clear();
+} // eo Stat::Stat()
+
+
+Stat::Stat(const std::string& path, bool follow_links)
+{
+    stat(path,follow_links);
+} // eo Stat::Stat(const std::string&,bool)
+
+
+Stat::~Stat()
+{
+} // eo Stat::~Stat()
+
+
+/**
+ * @brief updates the internal data.
+ *
+ * In other words: stat()'s the file again.
+ */
+void Stat::recheck()
+{
+    if (! m_path.empty())
+    {
+        stat(m_path, m_follow_links);
+    }
+} // eo Stat::recheck()
+
+
+/**
+ * @brief calls stat() or lstat() to get the information for the given path
+ *  and stores that information.
+ * @param path the path which should be checked
+ * @param follow_links determine if (symbalic) links should be followed.
+ */
+void Stat::stat(const std::string& path, bool follow_links)
+{
+    clear();
+    m_path= path;
+    m_follow_links= follow_links;
+    struct stat stat_info;
+    int res;
+    res = ( follow_links ? ::stat(path.c_str(), &stat_info) : ::lstat(path.c_str(), &stat_info) );
+    if ( 0 == res )
+    {
+        m_device = stat_info.st_dev;
+        m_inode  = stat_info.st_ino;
+        m_mode   = (stat_info.st_mode & ~(S_IFMT));
+        m_num_links = stat_info.st_nlink;
+        m_uid    = stat_info.st_uid;
+        m_gid    = stat_info.st_gid;
+        m_device_type = stat_info.st_rdev;
+        m_size   = stat_info.st_size;
+        m_atime  = stat_info.st_atime;
+        m_mtime  = stat_info.st_mtime;
+        m_ctime  = stat_info.st_atime;
+        
+        m_is_link=          S_ISLNK( stat_info.st_mode );
+        m_is_regular=       S_ISREG( stat_info.st_mode );
+        m_is_directory=     S_ISDIR( stat_info.st_mode );
+        m_is_character_device= S_ISCHR( stat_info.st_mode );
+        m_is_block_device=  S_ISBLK( stat_info.st_mode );
+        m_is_fifo=          S_ISFIFO( stat_info.st_mode );
+        m_is_socket=        S_ISSOCK( stat_info.st_mode );
+    }
+    m_valid = (0 == res);
+} // eo Stat::stat(const std::string&,bool)
 
-    if (stat(name.c_str(), &statbuff) < 0)
-        return false;
-    else
-        return true;
-}
 
-long fsize (const string &name)
+/**
+ * @brief clears the internal data.
+ */
+void Stat::clear()
+{
+    m_path.clear();
+    m_valid= false;
+    
+    m_device = 0;
+    m_inode  = 0;
+    m_mode   = 0;
+    m_num_links = 0;
+    m_uid    = 0;
+    m_gid    = 0;
+    m_device_type = 0;
+    m_size   = 0;
+    m_atime  = 0;
+    m_mtime  = 0;
+    m_ctime  = 0;
+    
+    m_is_link= false;
+    m_is_regular= false;
+    m_is_directory= false;
+    m_is_character_device= false;
+    m_is_block_device= false;
+    m_is_fifo= false;
+    m_is_socket= false;
+} // eo Stat::clear()
+
+
+/**
+ * @brief checks if another instance describes the same file.
+ * @param rhs the other instance.
+ * @return @a true iff the other instance describes the same file.
+ * @note
+ * The "same file" means that the files are located on the same device and use the same inode.
+ * They might still have two different directory entries (different paths)!
+ */
+bool Stat::isSameAs(const Stat& rhs)
+{
+    return m_valid and rhs.m_valid
+        and ( m_device == rhs.m_device)
+        and ( m_inode == rhs.m_inode);
+} // eo Stat::isSameAs(const Stat& rhs);
+
+
+/**
+ * @brief checks if this and the other instance describe the same device.
+ * @param rhs the other instance.
+ * @return @a true if we and the other instance describe a device and the same device.
+ *
+ * "Same device" means that the devices have the same type and the same major and minor id.
+ */
+bool Stat::isSameDeviceAs(const Stat& rhs)
+{
+    return isDevice() and rhs.isDevice()
+        and ( m_is_block_device == rhs.m_is_block_device )
+        and ( m_is_character_device == rhs.m_is_character_device )
+        and ( m_device_type == rhs.m_device_type);
+} // eo Stat::isSameDeviceAs(const Stat&)
+
+/**
+ * @brief check existence of a path.
+ * @param path path which should be tested.
+ * @return @a true iff path exists.
+ */
+bool path_exists(const std::string& path)
+{
+    struct stat stat_info;
+    int res = ::stat(path.c_str(), &stat_info);
+    if (res) return false;
+    return true;
+} // eo path_exists(const std::string&)
+
+/**
+ * @brief check existence of a regular file.
+ * @param path path which should be tested.
+ * @return @a true if path exists and is a regular file (or a link pointing to a regular file).
+ * @note this checks for regular files; not for the pure exitsnace of a path; use pathExists() for that.
+ * @see pathExists
+ */
+bool file_exists(const std::string& path)
+{
+    struct stat stat_info;
+    int res = ::stat(path.c_str(), &stat_info);
+    if (res) return false;
+    return S_ISREG(stat_info.st_mode);
+} // eo file_exists(const std::string&)
+
+// TODO: Use Stat class
+/**
+ * Get size of file
+ * @param name filename to get size for
+ * @return file size or -1 if file does not exist
+ */
+long file_size (const string &name)
 {
     long iReturn = -1;
 
@@ -50,54 +218,374 @@ long fsize (const string &name)
     return iReturn;
 }
 
-string load_file(const string &name)
+/**
+ * @brief tests the last modification time stamp of a path.
+ * @param path path which should be tested.
+ * @return the last modification time or 0 if the path doen't exist.
+ */
+time_t fileMTime(const std::string& path)
 {
-    FILE *f;
-    string s;
+    struct stat stat_info;
+    int res = ::stat(path.c_str(), &stat_info);
+    if (res) return 0;
+    return stat_info.st_mtime;
+} // eo fileMTime(const std::string&)
 
-    if (name.find ("..") != string::npos)
-        return ("can't load file (..): " + name);
 
-    f=::fopen(name.c_str(),"rb");
-    if (!f)
+/**
+ * @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.
+ * @param include_dot_names determines if dot-files should be included in the list.
+ * @return @a true if reading the directory was succesful, @a false on error.
+ */
+bool getDir(
+    const std::string& path,
+    std::vector< std::string >& result,
+    bool include_dot_names )
+{
+    DIR* dir = ::opendir( path.c_str());
+    if (!dir)
+    {
+        return false;
+    }
+    struct dirent *entry;
+    while ( NULL != (entry = ::readdir(dir)) )
     {
-        return ("");
+        std::string name( entry->d_name );
+        if (! include_dot_names && (name[0] == '.') )
+        {
+            continue;
+        }
+        result.push_back( name );
     }
+    ::closedir(dir);
+    return true;
+} // eo getDir(const std::string&,std::vector< std::string >&,bool)
+
+
+/**
+ * @brief reads the contents of a directory
+ * @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 list.
+ * @return the list of names (empty on error).
+ */
+std::vector< std::string > getDir(const std::string& path, bool include_dot_names )
+{
+    std::vector< std::string > result;
+    getDir(path,result,include_dot_names);
+    return result;
+} // eo getDir(const std::string&,bool)
 
-    ::fseek(f,0,SEEK_END);
-    int size=::ftell(f);
-    ::fseek(f,0,SEEK_SET);
-    char *c=new char[size+1];
-    ::fread(c,1,size,f);
-    s.assign(c,size);
-    delete[] c;
-    ::fclose (f);
 
-    return s;
-}
 
-bool chown(const char* file,const char* owner, const char* group)
+/**
+ * @brief removes a file from a filesystem.
+ * @param path path to the file.
+ * @return @a true iff the unlink was successful.
+ */
+bool unlink( const std::string& path )
 {
-    struct passwd *p;
-    struct group *g;
-    uid_t uid;
-    gid_t gid;
+    int res = ::unlink( path.c_str() );
+    return (res == 0);
+} // eo unlink(const std::string&)
+
 
-    p = getpwnam(owner);
-    if (p == NULL)
-        return false;
-    uid=p->pw_uid;
 
-    g = getgrnam(group);
-    if (g == NULL)
+/**
+ * @brief creates a symbolic link named @a link_name to @a target.
+ * @param target the target the link should point to.
+ * @param link_name the name of the link.
+ * @param force if @a true, the (file or link) @a link_name is removed if it exists.
+ * @return @a true iff the symlink was successfully created.
+ */
+bool symlink(const std::string& target, const std::string& link_name, bool force)
+{
+    int res= -1;
+    if (target.empty() or link_name.empty() or target == link_name)
+    {
+        // no, we don't do this!
         return false;
-    gid=g->gr_gid;
+    }
+    std::string n_target;
+    if (target[0] == '/') // absolute target?
+    {
+        n_target= target;
+    }
+    else // relative target
+    {
+        // for stat'ing: prepend dir of link_name:
+        n_target= dirname(link_name)+"/"+ target;
+    }
+    Stat target_stat(n_target, false);
+    Stat link_name_stat(link_name, false);
+    if (target_stat.exists() && link_name_stat.exists())
+    {
+        if (link_name_stat.isSameAs(target_stat)
+            or link_name_stat.isSameDeviceAs(target_stat) )
+        {
+            return false;
+        }
+        //TODO: more consistency checks?!
+    }
+    if (link_name_stat.exists())
+    {
+        // the link name already exists.
+        if (force)
+        {
+            // "force" as given, so try to remove the link_name:
+            unlink(link_name);
+            // update the stat:
+            link_name_stat.recheck();
+        }
+    }
+    if (link_name_stat.exists())
+    {
+        // well, if the link_name still exists; we cannot create that link:
+        errno = EEXIST;
+        return false;
+    }
+    res= ::symlink(target.c_str(), link_name.c_str());
+    return (0 == res);
+} // eo symlink(const std::string&,const std::string&,bool)
+
 
-    if (!::chown(file,uid,gid))
-        return true;
+
+/**
+ * @brief reads the target of a symbolic link.
+ * @param path path to the symbolic link
+ * @return the target of the link or an empty string on error.
+ */
+std::string readLink(const std::string& path)
+{
+    errno= 0;
+    Stat stat(path,false);
+    if (!stat || !stat.isLink())
+    {
+        return std::string();
+    }
+    int buffer_size= PATH_MAX+1 + 128;
+    boost::scoped_array<char> buffer_ptr( new char[buffer_size] );
+    int res= ::readlink( path.c_str(), buffer_ptr.get(), buffer_size-1 );
+    if (res >= 0)
+    {
+        return std::string( buffer_ptr.get(), res );
+    }
+    return std::string();
+} // eo readLink(const std::string&)
+
+
+
+/**
+ * @brief returns content of a file as string.
+ *
+ * A simple (q'n'd) function for retrieving content of a file as string.<br>
+ * Also able to read special (but regular) files which don't provide a size when stat'ed
+ * (like files in the /proc filesystem).
+ *
+ * @param path path to the file.
+ * @return the content of the file as string (empty if file could be opened).
+ */
+std::string readFile(const std::string& path)
+{
+    Stat stat(path);
+    if (!stat.isReg())
+    {
+        return std::string();
+    }
+    std::ifstream f( path.c_str(), std::ios::in | std::ios::binary );
+    std::string result;
+    if (f.is_open())
+    {
+        // NOTE: there are cases where we don't know the correct size (/proc files...)
+        // therefore we only use the size for reserving space if we know it, but don't
+        // use it when reading the file!
+        if (stat.size() > 0)
+        {
+            // if we know the size, we reserve enough space.
+            result.reserve( stat.size() );
+        }
+        char buffer[2048];
+        while (f.good())
+        {
+            f.read(buffer, sizeof(buffer));
+            result.append(buffer, f.gcount());
+        }
+    }
+    return result;
+} // eo readFile
+
+
+/**
+ * @brief writes a string to a file.
+ * @param path path to the file
+ * @param data the data which should be written into the file
+ * @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 writeFile(const std::string& path, const std::string& data)
+{
+    std::ofstream f( path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
+    if (f.good())
+    {
+        f.write( data.data(), data.size() );
+        return f.good();
+    }
     else
+    {
         return false;
-}
+    }
+} // eo writeFile
+
+
+/**
+ * @brief returns the filename part of a path (last component)
+ * @param path the path.
+ * @return the last component of the path.
+ */
+std::string basename(const std::string& path)
+{
+    std::string::size_type pos= path.rfind('/');
+    if (pos != std::string::npos)
+    {
+        return path.substr(pos+1);
+    }
+    return path;
+} // eo basename(const std::string&)
+
+
+/**
+ * @brief returns the directory part of a path.
+ * @param path the path.
+ * @return the directory part of the path.
+ */
+std::string dirname(const std::string& path)
+{
+    std::string::size_type pos= path.rfind('/');
+    if (pos != std::string::npos)
+    {
+        std::string result(path,0,pos);
+        if (result.empty())
+        {
+            return ".";
+        }
+        return result;
+    }
+    return ".";
+} // eo dirname(const std::string&)
+
+
+/**
+ * @brief normalizes a path.
+ *
+ * This method removes empty and "." elements.
+ * It also resolves ".." parts by removing previous path elements if possible.
+ * Leading ".." elements are preserved when a relative path was given; else they are removed.
+ * Trailing slashes are removed.
+ *
+ * @param path the path which should be normalized.
+ * @return the normalized path.
+ */
+
+std::string normalizePath(const std::string& path)
+{
+    if (path.empty())
+    {
+        return std::string();
+    }
+    // remember if the given path was absolute since this information vanishes when
+    // we split the path (since we split with omitting empty parts...)
+    bool is_absolute= (path[0]=='/');
+    std::list< std::string >  parts;
+    std::list< std::string >  result_parts;
+    
+    splitString(path,parts,"/",true);
+    
+    for(std::list< std::string >::const_iterator it_parts= parts.begin();
+        it_parts != parts.end();
+        ++it_parts)
+    {
+        std::string part(*it_parts); //convenience..
+        if (part == std::string(".") )
+        {
+            // single dot is "current path"; ignore!
+            continue;
+        }
+        if (part == std::string("..") )
+        {
+            // double dot is "one part back"
+            if (result_parts.empty())
+            {
+                if (is_absolute)
+                {
+                    // ignore since we cannot move behind / on absolute paths...
+                }
+                else
+                {
+                    // on relative path, we need to store the "..":
+                    result_parts.push_back(part);
+                }
+            }
+            else if (result_parts.back() == std::string("..") )
+            {
+                // if last element was already "..", we need to store the new one again...
+                // (PS: no need for "absolute" check; this can only be the case on relative path)
+                result_parts.push_back(part);
+            }
+            else
+            {
+                // remove last element.
+                result_parts.pop_back();
+            }
+            continue;
+        }
+        result_parts.push_back(part);
+    }
+    std::string result;
+    if (is_absolute)
+    {
+        result= "/";
+    }
+    result+= joinString(result_parts,"/");
+    return result;
+} // eo normalizePath(const std::string&)
+
+
+/**
+ * @brief changes the file(/path) mode.
+ * @param path the path to change the mode for.
+ * @param mode the new file mode.
+ * @return @a true iff the file mode was sucessfully changed.
+ */
+bool chmod(const std::string& path, int mode)
+{
+    int res= ::chmod(path.c_str(), mode);
+    return (res==0);
+} // eo chmod(const std::string&,int)
+
+
+/**
+ * @brief changed the owner of a file(/path)
+ * @param path the path to change the owner for.
+ * @param user the new file owner.
+ * @param group the new file group.
+ * @return @a true iff the file owner was succesfully changed.
+ *
+ * @note
+ * the validity of user and group within the system is not checked.
+ * This is intentional since this way we can use id's which are not assigned.
+ */
+bool chown(const std::string& path, const i2n::User& user, const i2n::Group& group)
+{
+    uid_t uid= user.uid;
+    if (uid<0) return false;
+    gid_t gid= group.gid;
+    if (gid<0) gid= user.gid;
+    if (gid<0) return false;
+    int res= ::chown( path.c_str(), uid, gid);
+    return (res==0);
+} // eo chown(const std::string&,const User&,const Group&)
 
 /**
  * Recursive delete of files and directories
@@ -162,3 +650,5 @@ bool recursive_delete(const std::string &path, std::string *error)
 
     return rtn;
 }
+
+}
index 14c9ddb..0b62cbb 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2004 by Gerd v. Egidy                                   *
+ *   Copyright (C) 2004-2008 by Intra2net AG                                    *
  *   info@intra2net.com                                                    *
  *                                                                         *
  ***************************************************************************/
 #ifndef __FILEFUNC_HXX
 #define __FILEFUNC_HXX
 
-bool file_exists (const std::string &name);
+#include "userfunc.hxx"
 
-long fsize (const std::string &name);
+namespace i2n
+{
 
-std::string load_file (const std::string &name);
+/**
+ * @brief helper class representing a file state.
+ *
+ * This basically wraps struct stat and provides some nicer access to the values.
+ */
+class Stat
+{
+    public:
+        Stat();
+        Stat(const std::string& path, bool follow_links= true);
+        ~Stat();
 
-bool chown(const char* file,const char* owner, const char* group);
+        void recheck();
+
+        bool isValid() const { return m_valid; }
+
+        bool exists() const { return m_valid; }
+        std::string path() const { return m_path; }
+
+        dev_t   device() const { return m_device; }
+        ino_t   inode() const { return m_inode; }
+        mode_t  mode() const { return m_mode; }
+        nlink_t nlink() const { return m_num_links; }
+        uid_t   uid() const { return m_uid; }
+        gid_t   gid() const { return m_gid; }
+        dev_t   rdev() const { return m_device_type; }
+        off_t   size() const { return m_size; }
+        time_t  atime() const { return m_atime; }
+        time_t  mtime() const { return m_mtime; }
+        time_t  ctime() const { return m_ctime; }
+
+        nlink_t numHardLinks() const { return m_num_links; }
+        dev_t   deviceType() const { return m_device_type; }
+        time_t  lastModifiedTime() const { return m_mtime; }
+        time_t  createdTime() const { return m_ctime; }
+
+        /*
+        ** unix style like type queries:
+        */
+
+        bool isLnk() const { return m_is_link; }
+        bool isReg() const { return m_is_regular; }
+        bool isDir() const { return m_is_directory; }
+        bool isChr() const { return m_is_character_device; }
+        bool isBlk() const { return m_is_block_device; }
+        bool isFifo() const { return m_is_fifo; }
+        bool isSock() const { return m_is_socket; }
+
+        /*
+        **  readable style type queries:
+        */
+
+        bool isLink () const { return m_is_link; }
+        bool isRegular() const { return m_is_regular; }
+        bool isRegularFile() const { return m_is_regular; }
+        bool isDirectory() const { return m_is_directory; }
+        bool isCharacterDevice() const {return m_is_character_device; }
+        bool isBlockDevice() const { return m_is_block_device; }
+        bool isFIFO() const { return m_is_fifo; }
+        bool isSocket() const { return m_is_socket; }
+
+        bool isDevice() const { return m_is_character_device or m_is_block_device; }
+
+
+        /*
+        ** "high level" queries
+        */
+
+        bool isSameAs(const Stat& rhs);
+        bool isSameDeviceAs(const Stat& rhs);
+
+        /*
+        ** convenience methods
+        */
+
+        operator bool() const { return m_valid; }
+
+    protected:
+
+        void stat(const std::string& path, bool follow_links= true);
+        void clear();
+
+    protected:
+        std::string m_path;
+        bool        m_follow_links;
+
+        bool m_valid;
+
+        dev_t   m_device;
+        ino_t   m_inode;
+        mode_t  m_mode;
+        nlink_t m_num_links;
+        uid_t   m_uid;
+        gid_t   m_gid;
+        dev_t   m_device_type;
+        off_t   m_size;
+        time_t  m_atime;
+        time_t  m_mtime;
+        time_t  m_ctime;
+
+        bool m_is_link : 1;
+        bool m_is_regular : 1;
+        bool m_is_directory : 1;
+        bool m_is_character_device : 1;
+        bool m_is_block_device : 1;
+        bool m_is_fifo : 1;
+        bool m_is_socket : 1;
+}; // eo class Stat
+
+/*
+** misc tool functions
+** (basically wrappers around system functions)
+*/
+
+bool path_exists(const std::string& path);
+bool file_exists(const std::string& path);
+long file_size (const std::string &name);
+
+time_t fileMTime(const std::string& path);
+
+bool getDir(const std::string& path, std::vector< std::string >& result, bool include_dot_names= false );
+std::vector< std::string > getDir(const std::string& path, bool include_dot_names= false );
+
+bool unlink(const std::string& path);
+
+bool symlink(const std::string& target, const std::string& link_name, bool force= false);
+
+std::string readLink(const std::string& path);
+
+
+std::string readFile(const std::string& path);
+
+bool writeFile(const std::string& path, const std::string& data);
+
+
+std::string basename(const std::string& path);
+
+std::string dirname(const std::string& path);
+
+std::string normalizePath(const std::string& path);
+
+bool chmod(const std::string& path, int mode);
+bool chown(const std::string& path, const i2n::User& user, const i2n::Group& group= i2n::Group());
 
 bool recursive_delete(const std::string &path, std::string *error=NULL);
 
+}
+
 #endif
index 8c67b97..7bf16f9 100644 (file)
@@ -1,10 +1,9 @@
-/***************************************************************************
-                          escape.cpp  -  escaping of strings
-                             -------------------
-    begin                : Sun Nov 14 1999
-    copyright            : (C) 1999 by Intra2net AG
-    email                : info@intra2net.com
- ***************************************************************************/
+/** @file
+ *
+ * (c) Copyright 2007-2008 by Intra2net AG
+ * 
+ * info@intra2net.com
+ */
 
 #include <iostream>
 #include <string>
 
 using namespace std;
 
+namespace i2n {
+
+
+namespace {
+
+const std::string hexDigitsLower("0123456789abcdef");
+const std::string hexDigitsUpper("0123456789ABCDEF");
+
+
+struct UpperFunc
+{
+    char operator() (char c)
+    {
+        return std::toupper(c);
+    }
+}; // eo struct UpperFunc
+
+
+struct LowerFunc
+{
+    char operator() (char c)
+    {
+        return std::tolower(c);
+    }
+}; // eo struct LowerFunc
+
+
+} // eo namespace <anonymous>
+
+
+
+/**
+ * default list of whitespaces (" \t\r\n");
+ */
+const std::string whitespaces = " \t\r\n";
+
+/**
+ * default list of lineendings ("\r\n");
+ */
+const std::string lineends= "\r\n";
+
+
+
+/**
+ * @brief checks if a string begins with a given prefix.
+ * @param[in,out] str the string which is tested
+ * @param prefix the prefix which should be tested for.
+ * @return @a true iff the prefix is not empty and the string begins with that prefix.
+ */
+bool hasPrefix(const std::string& str, const std::string& prefix)
+{
+    if (prefix.empty() || str.empty() || str.size() < prefix.size())
+    {
+        return false;
+    }
+    return str.compare(0, prefix.size(), prefix) == 0;
+} // eo hasPrefix(const std::string&,const std::string&)
+
+
+/**
+ * @brief checks if a string ends with a given suffix.
+ * @param[in,out] str the string which is tested
+ * @param suffix the suffix which should be tested for.
+ * @return @a true iff the suffix is not empty and the string ends with that suffix.
+ */
+bool hasSuffix(const std::string& str, const std::string& suffix)
+{
+    if (suffix.empty() || str.empty() || str.size() < suffix.size())
+    {
+        return false;
+    }
+    return str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
+} // eo hasSuffix(const std::string&,const std::string&)
+
+
+/**
+ * cut off characters from a given list from front and end of a string.
+ * @param[in,out] str the string which should be trimmed.
+ * @param charlist the list of characters to remove from beginning and end of string
+ * @return the result string.
+ */
+std::string trimMod(std::string& str, const std::string& charlist)
+{
+    // first: trim the beginning:
+    std::string::size_type pos= str.find_first_not_of(charlist);
+    if (pos == std::string::npos)
+    {
+        // whole string consists of charlist (or is already empty)
+        str.clear();
+        return str;
+    }
+    else if (pos>0)
+    {
+        // str starts with charlist
+        str.erase(0,pos);
+    }
+    // now let's look at the tail:
+    pos= str.find_last_not_of(charlist)+1;  // note: we already know there is at least one other char!
+    if ( pos < str.size() )
+    {
+        str.erase(pos, str.size()-pos);
+    }
+    return str;
+} // eo trimMod(std::string&,const std::string&)
+
+
+
+/**
+ * removes last character from a string when it is in a list of chars to be removed.
+ * @param[in,out] str the string.
+ * @param what the list of chars which will be tested for.
+ * @return the resulting string with last char removed (if applicable)
+ */
+std::string chompMod(std::string& str, const std::string& what)
+{
+    if (str.empty() || what.empty())
+    {
+        return str;
+    }
+    if (what.find( str.at(str.size()-1) ) != std::string::npos)
+    {
+        str.erase( str.size() - 1);
+    }
+    return str;
+} // eo chompMod(std::string&,const std::string&)
+
+
+/**
+ * @brief converts a string to lower case.
+ * @param[in,out] str the string to modify.
+ * @return the string
+ */
+std::string lowerMod(std::string& str)
+{
+    std::transform( str.begin(), str.end(), str.begin(), LowerFunc() );
+    return str;
+} // eo lowerMod(std::string&)
+
+
+/**
+ * @brief converts a string to upper case.
+ * @param[in,out] str the string to modify.
+ * @return the string
+ */
+std::string upperMod(std::string& str)
+{
+    std::transform( str.begin(), str.end(), str.begin(), UpperFunc() );
+    return str;
+} // eo upperMod(std::string&)
+
+
+
+/**
+ * cut off characters from a given list from front and end of a string.
+ * @param str the string which should be trimmed.
+ * @param charlist the list of characters to remove from beginning and end of string
+ * @return the result string.
+ */
+std::string trim(const std::string& str, const std::string& charlist)
+{
+    // first: trim the beginning:
+    std::string::size_type pos0= str.find_first_not_of(charlist);
+    if (pos0 == std::string::npos)
+    {
+        // whole string consists of charlist (or is already empty)
+        return std::string();
+    }
+    // now let's look at the end:
+    std::string::size_type pos1= str.find_last_not_of(charlist);
+    return str.substr(pos0, pos1 - pos0 + 1);
+} // eo trim(const std:.string&,const std::string&)
+
+
+/**
+ * removes last character from a string when it is in a list of chars to be removed.
+ * @param str the string.
+ * @param what the list of chars which will be tested for.
+ * @return the resulting string with last char removed (if applicable)
+ */
+std::string chomp(const std::string& str, const std::string& what)
+{
+    if (str.empty() || what.empty())
+    {
+        return str;
+    }
+    if (what.find( str.at(str.size()-1) ) != std::string::npos)
+    {
+        return str.substr(0, str.size()-1);
+    }
+    return str;
+} // eo chomp(const std:.string&,const std::string&)
+
+
+/**
+ * @brief returns a lower case version of a given string.
+ * @param str the string
+ * @return the lower case version of the string
+ */
+std::string lower(const std::string& str)
+{
+    std::string result(str);
+    return lowerMod(result);
+} // eo lower(const std::string&)
+
+
+/**
+ * @brief returns a upper case version of a given string.
+ * @param str the string
+ * @return the upper case version of the string
+ */
+std::string upper(const std::string& str)
+{
+    std::string result(str);
+    return upperMod(result);
+} // eo upper(const std::string&)
+
+
+
+/**
+ * @brief removes a given suffix from a string.
+ * @param str the string.
+ * @param suffix the suffix which should be removed if the string ends with it.
+ * @return the string without the suffix.
+ *
+ * If the string ends with the suffix, it is removed. If the the string doesn't end
+ * with the suffix the original string is returned.
+ */
+std::string removeSuffix(const std::string& str, const std::string& suffix)
+{
+    if (hasSuffix(str,suffix))
+    {
+        return str.substr(0, str.size()-suffix.size() );
+    }
+    return str;
+} // eo removeSuffix(const std::string&,const std::string&)
+
+
+
+/**
+ * @brief removes a given prefix from a string.
+ * @param str the string.
+ * @param prefix the prefix which should be removed if the string begins with it.
+ * @return the string without the prefix.
+ *
+ * If the string begins with the prefix, it is removed. If the the string doesn't begin
+ * with the prefix the original string is returned.
+ */
+std::string removePrefix(const std::string& str, const std::string& prefix)
+{
+    if (hasPrefix(str,prefix))
+    {
+        return str.substr( prefix.size() );
+    }
+    return str;
+} // eo removePrefix(const std::string&,const std::string&)
+
+
+/**
+ * split a string to key and value delimited by a given delimiter.
+ * The resulting key and value strings are trimmed (whitespaces removed at beginning and end).
+ * @param str the string which should be splitted.
+ * @param[out] key the resulting key
+ * @param[out] value the resulting value
+ * @param delimiter the delimiter between key and value; default is '='.
+ * @return @a true if the split was successful.
+ */
+bool pairSplit(
+    const std::string& str,
+    std::string& key,
+    std::string& value,
+    char delimiter)
+{
+    std::string::size_type pos = str.find(delimiter);
+    if (pos == std::string::npos) return false;
+    key= str.substr(0,pos);
+    value= str.substr(pos+1);
+    trimMod(key);
+    trimMod(value);
+    return true;
+} // eo pairSplit(const std::string&,std::string&,std::string&,char)
+
+
+/**
+ * splits a string by given delimiter
+ *
+ * @param[in] str the string which should be splitted.
+ * @param[out] result the list resulting from splitting  @a str.
+ * @param[in] delimiter the delimiter (word/phrase) at which @a str should be splitted.
+ * @param[in] omit_empty should empty parts not be stored?
+ * @param[in] trim_list list of characters the parts should be trimmed by.
+ *  (empty string results in no trim)
+ */
+void splitString(
+    const std::string& str,
+    std::list<std::string>& result,
+    const std::string& delimiter,
+    bool omit_empty,
+    const std::string& trim_list
+)
+{
+    std::string::size_type pos, last_pos=0;
+    bool delimiter_found= false;
+    while ( last_pos < str.size()  && last_pos != std::string::npos)
+    {
+        pos= str.find(delimiter, last_pos);
+        std::string part;
+        if (pos == std::string::npos)
+        {
+            part= str.substr(last_pos);
+            delimiter_found= false;
+        }
+        else
+        {
+            part= str.substr(last_pos, pos-last_pos);
+            delimiter_found=true;
+        }
+        if (pos != std::string::npos)
+        {
+            last_pos= pos+ delimiter.size();
+        }
+        else
+        {
+            last_pos= std::string::npos;
+        }
+        if (!trim_list.empty()) trimMod(part, trim_list);
+        if (omit_empty && part.empty()) continue;
+        result.push_back( part );
+    }
+    // if the string ends with a delimiter we need to append an empty string if no omit_empty
+    // was given.
+    // (this way we keep the split result consistent to a join operation)
+    if (delimiter_found && !omit_empty)
+    {
+        result.push_back("");
+    }
+} // eo splitString(const std::string&,std::list< std::string >&,const std::string&,bool,const std::string&)
+
+
+/**
+ * splits a string by a given delimiter
+ * @param str the string which should be splitted.
+ * @param delimiter delimiter the delimiter (word/phrase) at which @a str should be splitted.
+ * @param[in] omit_empty should empty parts not be stored?
+ * @param[in] trim_list list of characters the parts should be trimmed by.
+ *  (empty string results in no trim)
+ * @return the list resulting from splitting @a str.
+ */
+std::list<std::string> splitString(
+    const std::string& str,
+    const std::string& delimiter,
+    bool omit_empty,
+    const std::string& trim_list
+)
+{
+    std::list<std::string> result;
+    splitString(str, result, delimiter, omit_empty, trim_list);
+    return result;
+} // eo splitString(const std::string&,const std::string&,bool,const std::string&)
+
+
+/**
+ * @brief joins a list of strings into a single string.
+ *
+ * This funtion is (basically) the reverse operation of @a splitString.
+ * 
+ * @param parts the list of strings.
+ * @param delimiter the delimiter which is inserted between the strings.
+ * @return the joined string.
+ */
+std::string joinString(
+    const std::list< std::string >& parts,
+    const std::string& delimiter
+)
+{
+    std::string result;
+    if (! parts.empty())
+    {
+        std::list< std::string >::const_iterator it= parts.begin();
+        result = *it;
+        while( ++it != parts.end() )
+        {
+            result+= delimiter;
+            result+= *it;
+        }
+    }
+    return result;
+} // eo joinString(const std::list< std::string >&,const std::string&)
+
+
+
+/*
+** conversions
+*/
+
+
+/**
+ * @brief returns a hex string from a binary string.
+ * @param str the (binary) string
+ * @param upper_case_digits determine whether to use upper case characters for digits A-F.
+ * @return the string in hex notation.
+ */
+std::string binaryToHex(
+    const std::string& str,
+    bool upper_case_digits
+)
+{
+    std::string result;
+    std::string hexDigits( upper_case_digits ? hexDigitsUpper : hexDigitsLower);
+    for(std::string::const_iterator it= str.begin();
+        it != str.end();
+        ++it)
+    {
+        result.push_back( hexDigits[ ((*it) >> 4) & 0x0f ] );
+        result.push_back( hexDigits[ (*it) & 0x0f ] );
+    }
+    return result;
+} // eo binaryToHex(const std::string&,bool)
+
+
+/**
+ * @brief converts a hex digit string to binary string.
+ * @param str hex digit string
+ * @return the binary string.
+ *
+ * The hex digit string may contains white spaces or colons which are treated
+ * as delimiters between hex digit groups.
+ *
+ * @todo rework the handling of half nibbles (consistency)!
+ */
+std::string hexToBinary(
+    const std::string& str
+)
+throw(std::runtime_error)
+{
+    std::string result;
+    char c= 0;
+    bool hasNibble= false;
+    bool lastWasWS= true;
+    for(std::string::const_iterator it= str.begin();
+        it != str.end();
+        ++it)
+    {
+        std::string::size_type p = hexDigitsLower.find( *it );
+        if (p== std::string::npos)
+        {
+            p= hexDigitsUpper.find( *it );
+        }
+        if (p == std::string::npos)
+        {
+            if (   ( whitespaces.find( *it ) != std::string::npos) // is it a whitespace?
+                or ( *it == ':') // or a colon?
+               )
+            {
+                // we treat that as a valid delimiter:
+                if (hasNibble)
+                {
+                    // 1 nibble before WS is treate as lower part:
+                    result.push_back(c);
+                    // reset state:
+                    hasNibble= false;
+                }
+                lastWasWS= true;
+                continue;
+            }
+        }
+        if (p == std::string::npos )
+        {
+            throw runtime_error("illegal character in hex digit string: " + str);
+        }
+        lastWasWS= false;
+        if (hasNibble)
+        {
+            c<<=4;
+        }
+        else
+        {
+            c=0;
+        }
+        c+= (p & 0x0f);
+        if (hasNibble)
+        {
+            //we already had a nibble, so a char is complete now:
+            result.push_back( c );
+            hasNibble=false;
+        }
+        else
+        {
+            // this is the first nibble of a new char:
+            hasNibble=true;
+        }
+    }
+    if (hasNibble)
+    {
+        //well, there is one nibble left
+        // let's do some heuristics:
+        if (lastWasWS)
+        {
+            // if the preceeding character was a white space (or a colon)
+            // we treat the nibble as lower part:
+            //( this is consistent with shortened hex notations where leading zeros are not noted)
+            result.push_back( c );
+        }
+        else
+        {
+            // if it was part of a hex digit chain, we treat it as UPPER part (!!)
+            result.push_back( c << 4 );
+        }
+    }
+    return result;
+} // eo hexToBinary(const std::string&)
+
+
+} // eo namespace i2n
+
 std::string iso_to_utf8(const std::string& isostring)
 {
     string result;
index 196ae61..2802d36 100644 (file)
-/***************************************************************************
- *   Copyright (C) 2004 by Gerd v. Egidy                                   *
- *   info@intra2net.com                                                    *
- *                                                                         *
- ***************************************************************************/
+/** @file
+ * @brief collection of string tools (/ functions).
+ *
+ * contains a collection of miscellaneous functions for dealing with strings.
+ *
+ * some functions (like trim, lower case, upper case ...) are available in two versions:
+ * - a modifying one (suffix "Mod") which modifies the given string argument in place.
+ * - a non modifying one which take a constant string reference and returns a new string.
+ * .
+ *
+ *
+ * (c) Copyright 2007-2008 by Intra2net AG
+ * 
+ * info@intra2net.com
+ */
 
 #ifndef __STRINGFUNC_HXX
 #define __STRINGFUNC_HXX
 
+#include <list>
 #include <string>
+#include <sstream>
+#include <stdexcept>
+
+namespace i2n {
+
+/*
+** some useful constants:
+*/
+
+extern const std::string whitespaces;
+extern const std::string lineends;
+
+
+/*
+** predicates:
+*/
+
+
+bool hasPrefix(const std::string& str, const std::string& prefix);
+
+bool hasSuffix(const std::string& str, const std::string& suffix);
+
+
+/*
+** tool functions (modifying):
+*/
+
+std::string trimMod(std::string& str, const std::string& charlist = whitespaces);
+
+std::string chompMod(std::string& str, const std::string& what= lineends );
+
+std::string lowerMod(std::string& str);
+
+std::string upperMod(std::string& str);
+
+
+/*
+** tool functions (not modifying):
+*/
+
+std::string trim(const std::string& str, const std::string& charlist = whitespaces);
+
+std::string chomp(const std::string& str, const std::string& what= lineends );
+
+std::string lower(const std::string& str);
+
+std::string upper(const std::string& str);
+
+
+std::string removeSuffix(const std::string& str, const std::string& suffix);
+
+std::string removePrefix(const std::string& str, const std::string& prefix);
+
+
+
+/*
+** split and join:
+*/
+
+
+bool pairSplit(
+    const std::string& str,
+    std::string& key,
+    std::string& value,
+    char delimiter = '=');
+
+
+void splitString(
+    const std::string& str,
+    std::list< std::string >& result,
+    const std::string& delimiter= "\n",
+    bool omit_empty= false,
+    const std::string& trim_list= std::string()
+);
+
+std::list< std::string > splitString(
+    const std::string& str,
+    const std::string& delimiter = "\n",
+    bool omit_empty= false,
+    const std::string& trim_list= std::string()
+);
+
+
+std::string joinString(
+    const std::list< std::string >& parts,
+    const std::string& delimiter = "\n"
+);
+
+
+/*
+** conversions:
+*/
+
+
+std::string binaryToHex(const std::string&str, bool upper_case_digits= false);
+
+std::string hexToBinary(const std::string& str) throw(std::runtime_error);
+
+
+
+/*
+** "type conversions":
+*/
+
+
+/**
+ * convert a datatype @a T to a string via string stream.
+ *
+ * @param s the string which should be converted to @a T.
+ * @return the value of type T.
+ */
+template<
+    class T
+>
+T stringTo(const std::string& s)
+{
+    std::istringstream istr(s);
+    T result;
+    istr >> result;
+    return result;
+} // eo stringTo(const std::string&)
+
+
+/**
+ * convert a datatype @a T to a string via string stream.
+ *
+ * @param s the string which should be converted to @a T.
+ * @param result the resulting value of type @a T.
+ * @return @a true iff the internal string stream was EOF after the conversion.
+ */
+template<
+    class T
+>
+bool stringTo(const std::string& s, T& result)
+{
+    std::istringstream istr(s);
+    istr >> result;
+    return istr.eof();
+} // eo stringTo(const std::string&)
+
+
+/**
+ * convert a string to another datatype @a T via string stream.
+ *
+ * @param v the value (of type @a T) which should be converted to a string.
+ * @return the resulting string.
+ */
+template<
+    class T
+>
+std::string toString(const T& v)
+{
+    std::ostringstream ostr;
+    ostr << v;
+    return ostr.str();
+} // eo toString(const T&)
+
+} // eo namespace i2n
 
 std::string to_lower (const std::string &src);
 std::string to_upper (const std::string &src);