Add filesystem:: and filesystem::exchange, fix - assert_file creates an empty file...
authorMichael Andres <ma@suse.de>
Fri, 22 May 2009 13:32:19 +0000 (15:32 +0200)
committerMichael Andres <ma@suse.de>
Fri, 22 May 2009 13:32:19 +0000 (15:32 +0200)
  are created as needed.
- exchange exchanges two files or directories located on the same
  filesystem. Mainly used when creating new config files in a temp
  file and later exchanging it with the original one.
- fixed assert_dir returning OK, if the path alsready exists but isn't a
  directory.
- updated testcases

tests/zypp/PathInfo_test.cc
zypp/PathInfo.cc
zypp/PathInfo.h

index 132fd4a..2eb3b2c 100644 (file)
@@ -7,6 +7,7 @@
 #include <boost/test/auto_unit_test.hpp>
 
 #include "zypp/base/Logger.h"
+#include "zypp/base/LogControl.h"
 #include "zypp/base/Exception.h"
 #include "zypp/PathInfo.h"
 #include "zypp/TmpPath.h"
@@ -52,13 +53,13 @@ BOOST_AUTO_TEST_CASE(pathinfo_is_exist_test)
   Pathname subdir("text with spaces");
   // create a fake file
   BOOST_CHECK_EQUAL( filesystem::mkdir(dir.path() + subdir), 0 );
-  
+
   Pathname filepath = (dir.path() + subdir+ "filename");
   ofstream str(filepath.asString().c_str(),ofstream::out);
   str << "foo bar" << endl;
   str.flush();
   str.close();
-  
+
   BOOST_CHECK( PathInfo(filepath).isExist() );
 }
 
@@ -118,4 +119,100 @@ BOOST_AUTO_TEST_CASE(pathinfo_expandlink_test)
   cout << brokenlink << " -> " << filesystem::expandlink(brokenlink) << endl;
 }
 
+BOOST_AUTO_TEST_CASE(test_assert_dir_file)
+{
+  TmpDir   root;
+
+  Pathname rfile( root/"file" );
+  BOOST_CHECK_EQUAL( filesystem::assert_file( rfile ), 0 );
+  BOOST_CHECK( PathInfo(rfile).isFile() );
+
+  Pathname rdir ( root/"dir" );
+  BOOST_CHECK_EQUAL( filesystem::assert_dir ( rdir ),  0 );
+  BOOST_CHECK( PathInfo(rdir).isDir() );
+
+  // empty path
+  Pathname path;
+  BOOST_CHECK_EQUAL( filesystem::assert_file( path ), ENOENT );
+  BOOST_CHECK_EQUAL( filesystem::assert_dir ( path ), ENOENT );
+
+  // for dirs:
+  // existing dir
+  path = rdir;
+  BOOST_CHECK_EQUAL( filesystem::assert_dir( path ), 0 );
+  BOOST_CHECK( PathInfo(path).isDir() );
+  // new dirs
+  path = rdir/"sub/subsub";
+  BOOST_CHECK_EQUAL( filesystem::assert_dir( path ), 0 );
+  BOOST_CHECK( PathInfo(path).isDir() );
+  // file in path
+  path = rfile/"sub";
+  BOOST_CHECK_EQUAL( filesystem::assert_dir( path ), ENOTDIR );
+  BOOST_CHECK( !PathInfo(path).isDir() );
+  // path is file
+  path = rfile;
+  BOOST_CHECK_EQUAL( filesystem::assert_dir( path ), EEXIST );
+  BOOST_CHECK( !PathInfo(path).isDir() );
+
+  // for files:
+  // existing file
+  path = rfile;
+  BOOST_CHECK_EQUAL( filesystem::assert_file( path ), 0 );
+  BOOST_CHECK( PathInfo(path).isFile() );
+  // new file
+  path = rdir/"sub/file";
+  BOOST_CHECK_EQUAL( filesystem::assert_file( path ), 0 );
+  BOOST_CHECK( PathInfo(path).isFile() );
+  // file in path
+  path = rfile/"sub/file";
+  BOOST_CHECK_EQUAL( filesystem::assert_file( path ), ENOTDIR );
+  BOOST_CHECK( ! PathInfo(path).isFile() );
+  // path is dir
+  path = rdir;
+  BOOST_CHECK_EQUAL( filesystem::assert_file( path ), EEXIST );
+  BOOST_CHECK( ! PathInfo(path).isFile() );
+}
 
+BOOST_AUTO_TEST_CASE(test_exchange)
+{
+  TmpDir root;
+  Pathname a;
+  Pathname b;
+  // paths must not be epmty:
+  BOOST_CHECK_EQUAL( filesystem::exchange( a, b ), EINVAL );
+  a = root/"a/p";
+  BOOST_CHECK_EQUAL( filesystem::exchange( a, b ), EINVAL );
+  b = root/"b/p";
+  BOOST_CHECK_EQUAL( filesystem::exchange( a, b ), 0 ); // ok if both don't exist
+
+  // one path not existing:
+  filesystem::assert_file( a );
+  BOOST_CHECK( PathInfo(a).isFile() );
+  BOOST_CHECK( !PathInfo(b).isFile() );
+
+  BOOST_CHECK_EQUAL( filesystem::exchange( a, b ), 0 );
+  BOOST_CHECK( !PathInfo(a).isFile() );
+  BOOST_CHECK( PathInfo(b).isFile() );
+
+  BOOST_CHECK_EQUAL( filesystem::exchange( a, b ), 0 );
+  BOOST_CHECK( PathInfo(a).isFile() );
+  BOOST_CHECK( !PathInfo(b).isFile() );
+
+  // both paths exist:
+  filesystem::assert_dir( b );
+  BOOST_CHECK( PathInfo(b).isDir() );
+
+  BOOST_CHECK_EQUAL( filesystem::exchange( a, b ), 0 );
+  BOOST_CHECK( PathInfo(a).isDir() );
+  BOOST_CHECK( PathInfo(b).isFile() );
+
+  BOOST_CHECK_EQUAL( filesystem::exchange( a, b ), 0 );
+  BOOST_CHECK( PathInfo(a).isFile() );
+  BOOST_CHECK( PathInfo(b).isDir() );
+
+  // Exchange with location that can't be created:
+  BOOST_CHECK_EQUAL( chmod( b.dirname(), 0555 ), 0 );
+  BOOST_CHECK_EQUAL( filesystem::exchange( a, b ), EACCES );
+  BOOST_CHECK( PathInfo(a).isFile() );
+  BOOST_CHECK( PathInfo(b).isDir() );
+}
index 3a4b4cf..c8ebe3d 100644 (file)
@@ -25,6 +25,7 @@
 #include "zypp/ExternalProgram.h"
 #include "zypp/PathInfo.h"
 #include "zypp/Digest.h"
+#include "zypp/TmpPath.h"
 
 using std::endl;
 using std::string;
@@ -287,11 +288,16 @@ namespace zypp
      **
      **        DESCRIPTION : Helper function to log return values.
     */
-    inline int _Log_Result( const int res, const char * rclass = "errno" )
+#define _Log_Result MIL << endl, __Log_Result
+    inline int __Log_Result( const int res, const char * rclass = 0 /*errno*/ )
     {
-      MIL << endl;
       if ( res )
-        WAR << " FAILED: " << rclass << " " << res << endl;
+      {
+        if ( rclass )
+          WAR << " FAILED: " << rclass << " " << res << endl;
+        else
+          WAR << " FAILED: " << str::strerror( res ) << endl;
+      }
       return res;
     }
 
@@ -316,42 +322,43 @@ namespace zypp
     //
     int assert_dir( const Pathname & path, unsigned mode )
     {
-      string::size_type pos, lastpos = 0;
-      string spath = path.asString()+"/";
-      int ret = 0;
-
-      if(path.empty())
+      if ( path.empty() )
         return ENOENT;
 
-      // skip ./
-      if(path.relative())
-        lastpos=2;
-      // skip /
-      else
-        lastpos=1;
+      { // Handle existing paths in advance.
+        PathInfo pi( path );
+        if ( pi.isDir() )
+          return 0;
+        if ( pi.isExist() )
+          return EEXIST;
+      }
 
-      //    MIL << "about to create " << spath << endl;
-      while((pos = spath.find('/',lastpos)) != string::npos )
+      string spath = path.asString()+"/";
+      string::size_type lastpos = ( path.relative() ? 2 : 1 ); // skip leasding './' or '/'
+      string::size_type pos = string::npos;
+      int ret = 0;
+
+      while ( (pos = spath.find('/',lastpos)) != string::npos )
+      {
+        string dir( spath.substr(0,pos) );
+        ret = ::mkdir( dir.c_str(), mode );
+        if ( ret == -1 )
         {
-          string dir = spath.substr(0,pos);
-          ret = ::mkdir(dir.c_str(), mode);
-          if(ret == -1)
-          {
-            // ignore errors about already existing directorys
-            if(errno == EEXIST)
-              ret=0;
-            else
-            {
-              ret=errno;
-              WAR << " FAILED: mkdir " << dir << ' ' << str::octstring( mode ) << " errno " << ret << endl;
-            }
-          }
+          if ( errno == EEXIST ) // ignore errors about already existing paths
+            ret = 0;
           else
           {
-            MIL << "mkdir " << dir << ' ' << str::octstring( mode ) << endl;
+            ret = errno;
+            WAR << " FAILED: mkdir " << dir << ' ' << str::octstring( mode ) << " errno " << ret << endl;
           }
-          lastpos = pos+1;
         }
+        else
+        {
+          MIL << "mkdir " << dir << ' ' << str::octstring( mode ) << endl;
+        }
+        lastpos = pos+1;
+      }
+
       return ret;
     }
 
@@ -679,6 +686,70 @@ namespace zypp
 
     ///////////////////////////////////////////////////////////////////
     //
+    // METHOD NAME : exchange
+    // METHOD TYPE : int
+    //
+    int exchange( const Pathname & lpath, const Pathname & rpath )
+    {
+      MIL << "exchange " << lpath << " <-> " << rpath;
+      if ( lpath.empty() || rpath.empty() )
+        return _Log_Result( EINVAL );
+
+      PathInfo linfo( lpath );
+      PathInfo rinfo( rpath );
+
+      if ( ! linfo.isExist() )
+      {
+        if ( ! rinfo.isExist() )
+          return _Log_Result( 0 ); // both don't exist.
+
+        // just rename rpath -> lpath
+        int ret = assert_dir( lpath.dirname() );
+        if ( ret != 0 )
+          return _Log_Result( ret );
+        if ( ::rename( rpath.c_str(), lpath.c_str() ) == -1 ) {
+          return _Log_Result( errno );
+        }
+        return _Log_Result( 0 );
+      }
+
+      // HERE: lpath exists:
+      if ( ! rinfo.isExist() )
+      {
+        // just rename lpath -> rpath
+        int ret = assert_dir( rpath.dirname() );
+        if ( ret != 0 )
+          return _Log_Result( ret );
+        if ( ::rename( lpath.c_str(), rpath.c_str() ) == -1 ) {
+          return _Log_Result( errno );
+        }
+        return _Log_Result( 0 );
+      }
+
+      // HERE: both exist
+      TmpFile tmpfile( TmpFile::makeSibling( rpath ) );
+      if ( ! tmpfile )
+        return _Log_Result( errno );
+      Pathname tmp( tmpfile.path() );
+      ::unlink( tmp.c_str() );
+
+      if ( ::rename( lpath.c_str(), tmp.c_str() ) == -1 ) {
+        return _Log_Result( errno );
+      }
+      if ( ::rename( rpath.c_str(), lpath.c_str() ) == -1 ) {
+        ::rename( tmp.c_str(), lpath.c_str() );
+        return _Log_Result( errno );
+      }
+      if ( ::rename( tmp.c_str(), rpath.c_str() ) == -1 ) {
+        ::rename( lpath.c_str(), rpath.c_str() );
+        ::rename( tmp.c_str(), lpath.c_str() );
+        return _Log_Result( errno );
+      }
+      return _Log_Result( 0 );
+    }
+
+    ///////////////////////////////////////////////////////////////////
+    //
     // METHOD NAME : copy
     // METHOD TYPE : int
     //
@@ -979,6 +1050,30 @@ namespace zypp
 
     ///////////////////////////////////////////////////////////////////
     //
+    // METHOD NAME : getUmask
+    // METHOD TYPE : mode_t
+    //
+    int assert_file( const Pathname & path, unsigned mode )
+    {
+      int ret = assert_dir( path.dirname() );
+      MIL << "assert_file " << str::octstring( mode ) << " " << path;
+      if ( ret != 0 )
+        return _Log_Result( ret );
+
+      PathInfo pi( path );
+      if ( pi.isExist() )
+        return _Log_Result( pi.isFile() ? 0 : EEXIST );
+
+      int fd = ::creat( path.c_str(), mode );
+      if ( fd == -1 )
+        return _Log_Result( errno );
+
+      ::close( fd );
+      return _Log_Result( 0 );
+    }
+
+    ///////////////////////////////////////////////////////////////////
+    //
     //  METHOD NAME : touch
     //  METHOD TYPE : int
     //
index bb88e80..2dbd19f 100644 (file)
@@ -523,6 +523,15 @@ namespace zypp
     /** \name File related functions. */
     //@{
     /**
+     * Create an empty file if it does not yet exist. Make parent directories
+     * as needed. mode specifies the permissions to use. It is modified by the
+     * process's umask in the usual way.
+     *
+     * @return 0 on success, errno on failure
+     **/
+    int assert_file( const Pathname & path, unsigned mode = 0644 );
+
+    /**
      * Change file's modification and access times.
      *
      * \return 0 on success, errno on failure
@@ -544,6 +553,34 @@ namespace zypp
      **/
     int rename( const Pathname & oldpath, const Pathname & newpath );
 
+    /** Exchanges two files or directories.
+     *
+     * Most common use is when building a new config file (or dir)
+     * in a tempfile. After the job is done, configfile and tempfile
+     * are exchanged. This includes moving away the configfile in case
+     * the tempfile does not exist. Parent directories are created as
+     * needed.
+     *
+     * \note Paths are exchanged using \c ::rename, so take care both paths
+     * are located on the same filesystem.
+     *
+     * \code
+     * Pathname configfile( "/etc/myconfig" );
+     * TmpFile  newconfig( TmpFile::makeSibling( configfile ) );
+     * // now write the new config:
+     * std::ofstream o( newconfig.path().c_str() );
+     * o << "mew values << endl;
+     * o.close();
+     * // If everything is fine, exchange the files:
+     * exchange( newconfig.path(), configfile );
+     * // Now the old configfile is still available at newconfig.path()
+     * // until newconfig goes out of scope.
+     * \endcode
+     *
+     * @return 0 on success, errno on failure
+     */
+    int exchange( const Pathname & lpath, const Pathname & rpath );
+
     /**
      * Like 'cp file dest'. Copy file to destination file.
      *
@@ -586,7 +623,7 @@ namespace zypp
     /**
      * Recursively follows the symlink pointed to by \a path_r and returns
      * the Pathname to the real file or directory pointed to by the link.
-     * 
+     *
      * There is a recursion limit of 256 iterations to protect against a cyclic
      * link.
      *
@@ -594,7 +631,7 @@ namespace zypp
      *   if it is a valid link. If \a path_r is not a link, an exact copy of
      *   it is returned. If \a path_r is a broken or a cyclic link, an empty
      *   Pathname is returned and the event logged.
-     */ 
+     */
     Pathname expandlink( const Pathname & path_r );
 
     /**