From: Michael Andres Date: Fri, 22 May 2009 13:32:19 +0000 (+0200) Subject: Add filesystem:: and filesystem::exchange, fix - assert_file creates an empty file... X-Git-Tag: 6.6.0~9 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=5aa014d6ddd28d2170bab96d5f8e35005bc948db;p=platform%2Fupstream%2Flibzypp.git Add filesystem:: and filesystem::exchange, fix - assert_file creates an empty file if it not yet exists. Parent dirs 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 --- diff --git a/tests/zypp/PathInfo_test.cc b/tests/zypp/PathInfo_test.cc index 132fd4a..2eb3b2c 100644 --- a/tests/zypp/PathInfo_test.cc +++ b/tests/zypp/PathInfo_test.cc @@ -7,6 +7,7 @@ #include #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() ); +} diff --git a/zypp/PathInfo.cc b/zypp/PathInfo.cc index 3a4b4cf..c8ebe3d 100644 --- a/zypp/PathInfo.cc +++ b/zypp/PathInfo.cc @@ -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 // diff --git a/zypp/PathInfo.h b/zypp/PathInfo.h index bb88e80..2dbd19f 100644 --- a/zypp/PathInfo.h +++ b/zypp/PathInfo.h @@ -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 ); /**