PathInfo.cc

Go to the documentation of this file.
00001 /*---------------------------------------------------------------------\
00002 |                          ____ _   __ __ ___                          |
00003 |                         |__  / \ / / . \ . \                         |
00004 |                           / / \ V /|  _/  _/                         |
00005 |                          / /__ | | | | | |                           |
00006 |                         /_____||_| |_| |_|                           |
00007 |                                                                      |
00008 \---------------------------------------------------------------------*/
00013 #include <sys/types.h> // for ::minor, ::major macros
00014 
00015 #include <iostream>
00016 #include <fstream>
00017 #include <iomanip>
00018 
00019 #include <boost/filesystem/operations.hpp>
00020 #include <boost/filesystem/exception.hpp>
00021 
00022 #include "zypp/base/Logger.h"
00023 #include "zypp/base/String.h"
00024 #include "zypp/base/IOStream.h"
00025 
00026 #include "zypp/ExternalProgram.h"
00027 #include "zypp/PathInfo.h"
00028 #include "zypp/Digest.h"
00029 
00030 
00031 using std::string;
00032 
00034 namespace zypp
00035 { 
00036 
00037   namespace filesystem
00038   { 
00039 
00040     /******************************************************************
00041      **
00042      ** FUNCTION NAME : operator<<
00043      ** FUNCTION TYPE : std::ostream &
00044     */
00045     std::ostream & operator<<( std::ostream & str, FileType obj )
00046     {
00047       switch ( obj ) {
00048 #define EMUMOUT(T) case T: return str << #T; break
00049         EMUMOUT( FT_NOT_AVAIL );
00050         EMUMOUT( FT_NOT_EXIST );
00051         EMUMOUT( FT_FILE );
00052         EMUMOUT( FT_DIR );
00053         EMUMOUT( FT_CHARDEV );
00054         EMUMOUT( FT_BLOCKDEV );
00055         EMUMOUT( FT_FIFO );
00056         EMUMOUT( FT_LINK );
00057         EMUMOUT( FT_SOCKET );
00058 #undef EMUMOUT
00059       }
00060       return str;
00061     }
00062 
00064     //
00065     //  METHOD NAME : StatMode::fileType
00066     //  METHOD TYPE : FileType
00067     //
00068     FileType StatMode::fileType() const
00069     {
00070       if ( isFile() )
00071         return FT_FILE;
00072       if ( isDir() )
00073         return FT_DIR;
00074       if ( isLink() )
00075         return FT_LINK;
00076       if ( isChr() )
00077         return FT_CHARDEV;
00078       if ( isBlk() )
00079         return FT_BLOCKDEV;
00080       if ( isFifo() )
00081         return FT_FIFO;
00082       if ( isSock() )
00083         return FT_SOCKET ;
00084 
00085       return FT_NOT_AVAIL;
00086     }
00087 
00088     /******************************************************************
00089      **
00090      ** FUNCTION NAME : operator<<
00091      ** FUNCTION TYPE : std::ostream &
00092     */
00093     std::ostream & operator<<( std::ostream & str, const StatMode & obj )
00094     {
00095       iostr::IosFmtFlagsSaver autoResoreState( str );
00096 
00097       char t = '?';
00098       if ( obj.isFile() )
00099         t = '-';
00100       else if ( obj.isDir() )
00101         t = 'd';
00102       else if ( obj.isLink() )
00103         t = 'l';
00104       else if ( obj.isChr() )
00105         t = 'c';
00106       else if ( obj.isBlk() )
00107         t = 'b';
00108       else if ( obj.isFifo() )
00109         t = 'p';
00110       else if ( obj.isSock() )
00111         t = 's';
00112 
00113       str << t << " " << std::setfill( '0' ) << std::setw( 4 ) << std::oct << obj.perm();
00114       return str;
00115     }
00116 
00118     //
00119     //  Class : PathInfo
00120     //
00122 
00124     //
00125     //  METHOD NAME : PathInfo::PathInfo
00126     //  METHOD TYPE : Constructor
00127     //
00128     PathInfo::PathInfo()
00129     : mode_e( STAT )
00130     , error_i( -1 )
00131     {}
00132 
00134     //
00135     //  METHOD NAME : PathInfo::PathInfo
00136     //  METHOD TYPE : Constructor
00137     //
00138     PathInfo::PathInfo( const Pathname & path, Mode initial )
00139     : path_t( path )
00140     , mode_e( initial )
00141     , error_i( -1 )
00142     {
00143       operator()();
00144     }
00145 
00147     //
00148     //  METHOD NAME : PathInfo::PathInfo
00149     //  METHOD TYPE : Constructor
00150     //
00151     PathInfo::PathInfo( const std::string & path, Mode initial )
00152     : path_t( path )
00153     , mode_e( initial )
00154     , error_i( -1 )
00155     {
00156       operator()();
00157     }
00158 
00160     //
00161     //  METHOD NAME : PathInfo::PathInfo
00162     //  METHOD TYPE : Constructor
00163     //
00164     PathInfo::PathInfo( const char * path, Mode initial )
00165     : path_t( path )
00166     , mode_e( initial )
00167     , error_i( -1 )
00168     {
00169       operator()();
00170     }
00171 
00173     //
00174     //  METHOD NAME : PathInfo::~PathInfo
00175     //  METHOD TYPE : Destructor
00176     //
00177     PathInfo::~PathInfo()
00178     {
00179     }
00180 
00182     //
00183     //  METHOD NAME : PathInfo::operator()
00184     //  METHOD TYPE : bool
00185     //
00186     bool PathInfo::operator()()
00187     {
00188       if ( path_t.empty() ) {
00189         error_i = -1;
00190       } else {
00191         switch ( mode_e ) {
00192         case STAT:
00193           error_i = ::stat( path_t.asString().c_str(), &statbuf_C );
00194           break;
00195         case LSTAT:
00196           error_i = ::lstat( path_t.asString().c_str(), &statbuf_C );
00197           break;
00198         }
00199         if ( error_i == -1 )
00200           error_i = errno;
00201       }
00202       return !error_i;
00203     }
00204 
00206     //
00207     //  METHOD NAME : PathInfo::fileType
00208     //  METHOD TYPE : File_type
00209     //
00210     FileType PathInfo::fileType() const
00211     {
00212       if ( isExist() )
00213         return asStatMode().fileType();
00214       return FT_NOT_EXIST;
00215     }
00216 
00218     //
00219     //  METHOD NAME : PathInfo::userMay
00220     //  METHOD TYPE : mode_t
00221     //
00222     mode_t PathInfo::userMay() const
00223     {
00224       if ( !isExist() )
00225         return 0;
00226       if ( owner() == getuid() ) {
00227         return( uperm()/0100 );
00228       } else if ( group() == getgid() ) {
00229         return( gperm()/010 );
00230       }
00231       return operm();
00232     }
00233 
00234     /******************************************************************
00235      **
00236      ** FUNCTION NAME : PathInfo::major
00237      ** FUNCTION TYPE : unsigned int
00238      */
00239     unsigned int PathInfo::major() const
00240     {
00241       return isBlk() || isChr() ? ::major(statbuf_C.st_rdev) : 0;
00242     }
00243 
00244     /******************************************************************
00245      **
00246      ** FUNCTION NAME : PathInfo::minor
00247      ** FUNCTION TYPE : unsigned int
00248      */
00249     unsigned int PathInfo::minor() const
00250     {
00251       return isBlk() || isChr() ? ::minor(statbuf_C.st_rdev) : 0;
00252     }
00253 
00254     /******************************************************************
00255      **
00256      ** FUNCTION NAME : operator<<
00257      ** FUNCTION TYPE :  std::ostream &
00258     */
00259     std::ostream & operator<<( std::ostream & str, const PathInfo & obj )
00260     {
00261       iostr::IosFmtFlagsSaver autoResoreState( str );
00262 
00263       str << obj.asString() << "{";
00264       if ( !obj.isExist() ) {
00265         str << "does not exist}";
00266       } else {
00267         str << obj.asStatMode() << " " << std::dec << obj.owner() << "/" << obj.group();
00268 
00269         if ( obj.isFile() )
00270           str << " size " << obj.size();
00271 
00272         str << "}";
00273       }
00274 
00275       return str;
00276     }
00277 
00279     //
00280     //  filesystem utilities
00281     //
00283 
00284     /******************************************************************
00285      **
00286      ** FUNCTION NAME : _Log_Result
00287      ** FUNCTION TYPE : int
00288      **
00289      ** DESCRIPTION : Helper function to log return values.
00290     */
00291     inline int _Log_Result( const int res, const char * rclass = "errno" )
00292     {
00293       if ( res )
00294         DBG << " FAILED: " << rclass << " " << res;
00295       DBG << std::endl;
00296       return res;
00297     }
00298 
00300     //
00301     //  METHOD NAME : PathInfo::mkdir
00302     //  METHOD TYPE : int
00303     //
00304     int mkdir( const Pathname & path, unsigned mode )
00305     {
00306       DBG << "mkdir " << path << ' ' << str::octstring( mode );
00307       if ( ::mkdir( path.asString().c_str(), mode ) == -1 ) {
00308         return _Log_Result( errno );
00309       }
00310       return _Log_Result( 0 );
00311     }
00312 
00314     //
00315     //  METHOD NAME : assert_dir()
00316     //  METHOD TYPE : int
00317     //
00318     int assert_dir( const Pathname & path, unsigned mode )
00319     {
00320       string::size_type pos, lastpos = 0;
00321       string spath = path.asString()+"/";
00322       int ret = 0;
00323 
00324       if(path.empty())
00325         return ENOENT;
00326 
00327       // skip ./
00328       if(path.relative())
00329         lastpos=2;
00330       // skip /
00331       else
00332         lastpos=1;
00333 
00334       //    DBG << "about to create " << spath << endl;
00335       while((pos = spath.find('/',lastpos)) != string::npos )
00336         {
00337           string dir = spath.substr(0,pos);
00338           ret = ::mkdir(dir.c_str(), mode);
00339           if(ret == -1)
00340             {
00341               // ignore errors about already existing directorys
00342               if(errno == EEXIST)
00343                 ret=0;
00344               else
00345                 ret=errno;
00346             }
00347           //    DBG << "creating directory " << dir << (ret?" failed":" succeeded") << endl;
00348           lastpos = pos+1;
00349         }
00350       return ret;
00351     }
00352 
00354     //
00355     //  METHOD NAME : rmdir
00356     //  METHOD TYPE : int
00357     //
00358     int rmdir( const Pathname & path )
00359     {
00360       DBG << "rmdir " << path;
00361       if ( ::rmdir( path.asString().c_str() ) == -1 ) {
00362         return _Log_Result( errno );
00363       }
00364       return _Log_Result( 0 );
00365     }
00366 
00368     //
00369     //  METHOD NAME : recursive_rmdir
00370     //  METHOD TYPE : int
00371     //
00372     int recursive_rmdir( const Pathname & path )
00373     {
00374       DBG << "recursive_rmdir " << path << ' ';
00375       PathInfo p( path );
00376 
00377       if ( !p.isExist() ) {
00378         return _Log_Result( 0 );
00379       }
00380 
00381       if ( !p.isDir() ) {
00382         return _Log_Result( ENOTDIR );
00383       }
00384 
00385       try
00386         {
00387           boost::filesystem::path bp( path.asString(), boost::filesystem::native );
00388           boost::filesystem::remove_all( bp );
00389         }
00390       catch ( boost::filesystem::filesystem_error & excpt )
00391         {
00392           DBG << " FAILED: " << excpt.what() << std::endl;
00393           return -1;
00394         }
00395 
00396       return _Log_Result( 0 );
00397     }
00398 
00400     //
00401     //  METHOD NAME : clean_dir
00402     //  METHOD TYPE : int
00403     //
00404     int clean_dir( const Pathname & path )
00405     {
00406       DBG << "clean_dir " << path << ' ';
00407       PathInfo p( path );
00408 
00409       if ( !p.isExist() ) {
00410         return _Log_Result( 0 );
00411       }
00412 
00413       if ( !p.isDir() ) {
00414         return _Log_Result( ENOTDIR );
00415       }
00416 
00417       string cmd( str::form( "cd '%s' && rm -rf --preserve-root -- *", path.asString().c_str() ) );
00418       ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout );
00419       for ( string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
00420         DBG << "  " << output;
00421       }
00422       int ret = prog.close();
00423       return _Log_Result( ret, "returned" );
00424     }
00425 
00427     //
00428     //  METHOD NAME : copy_dir
00429     //  METHOD TYPE : int
00430     //
00431     int copy_dir( const Pathname & srcpath, const Pathname & destpath )
00432     {
00433       DBG << "copy_dir " << srcpath << " -> " << destpath << ' ';
00434 
00435       PathInfo sp( srcpath );
00436       if ( !sp.isDir() ) {
00437         return _Log_Result( ENOTDIR );
00438       }
00439 
00440       PathInfo dp( destpath );
00441       if ( !dp.isDir() ) {
00442         return _Log_Result( ENOTDIR );
00443       }
00444 
00445       PathInfo tp( destpath + srcpath.basename() );
00446       if ( tp.isExist() ) {
00447         return _Log_Result( EEXIST );
00448       }
00449 
00450 
00451       const char *const argv[] = {
00452         "/bin/cp",
00453         "-dR",
00454         "--",
00455         srcpath.asString().c_str(),
00456         destpath.asString().c_str(),
00457         NULL
00458       };
00459       ExternalProgram prog( argv, ExternalProgram::Stderr_To_Stdout );
00460       for ( string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
00461         DBG << "  " << output;
00462       }
00463       int ret = prog.close();
00464       return _Log_Result( ret, "returned" );
00465     }
00466 
00468     //
00469     //  METHOD NAME : copy_dir_content
00470     //  METHOD TYPE : int
00471     //
00472     int copy_dir_content(const Pathname & srcpath, const Pathname & destpath)
00473     {
00474       DBG << "copy_dir " << srcpath << " -> " << destpath << ' ';
00475 
00476       PathInfo sp( srcpath );
00477       if ( !sp.isDir() ) {
00478         return _Log_Result( ENOTDIR );
00479       }
00480 
00481       PathInfo dp( destpath );
00482       if ( !dp.isDir() ) {
00483         return _Log_Result( ENOTDIR );
00484       }
00485 
00486       if ( srcpath == destpath ) {
00487         return _Log_Result( EEXIST );
00488       }
00489 
00490       std::string src( srcpath.asString());
00491       src += "/.";
00492       const char *const argv[] = {
00493         "/bin/cp",
00494         "-dR",
00495         "--",
00496         src.c_str(),
00497         destpath.asString().c_str(),
00498         NULL
00499       };
00500       ExternalProgram prog( argv, ExternalProgram::Stderr_To_Stdout );
00501       for ( string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
00502         DBG << "  " << output;
00503       }
00504       int ret = prog.close();
00505       return _Log_Result( ret, "returned" );
00506     }
00507 
00509     //
00510     //  METHOD NAME : readdir
00511     //  METHOD TYPE : int
00512     //
00513     int readdir( std::list<std::string> & retlist,
00514                  const Pathname & path, bool dots )
00515     {
00516       retlist.clear();
00517 
00518       DBG << "readdir " << path << ' ';
00519 
00520       DIR * dir = ::opendir( path.asString().c_str() );
00521       if ( ! dir ) {
00522         return _Log_Result( errno );
00523       }
00524 
00525       struct dirent *entry;
00526       while ( (entry = ::readdir( dir )) != 0 ) {
00527 
00528         if ( entry->d_name[0] == '.' ) {
00529           if ( !dots )
00530             continue;
00531           if ( entry->d_name[1] == '\0'
00532                || (    entry->d_name[1] == '.'
00533                     && entry->d_name[2] == '\0' ) )
00534             continue;
00535         }
00536         retlist.push_back( entry->d_name );
00537       }
00538 
00539       ::closedir( dir );
00540 
00541       return _Log_Result( 0 );
00542     }
00543 
00544 
00546     //
00547     //  METHOD NAME : readdir
00548     //  METHOD TYPE : int
00549     //
00550     int readdir( std::list<Pathname> & retlist,
00551                  const Pathname & path, bool dots )
00552     {
00553       retlist.clear();
00554 
00555       std::list<string> content;
00556       int res = readdir( content, path, dots );
00557 
00558       if ( !res ) {
00559         for ( std::list<string>::const_iterator it = content.begin(); it != content.end(); ++it ) {
00560           retlist.push_back( path + *it );
00561         }
00562       }
00563 
00564       return res;
00565     }
00566 
00568     //
00569     //  METHOD NAME : readdir
00570     //  METHOD TYPE : int
00571     //
00572     int readdir( DirContent & retlist, const Pathname & path,
00573                  bool dots, PathInfo::Mode statmode )
00574     {
00575       retlist.clear();
00576 
00577       std::list<string> content;
00578       int res = readdir( content, path, dots );
00579 
00580       if ( !res ) {
00581         for ( std::list<string>::const_iterator it = content.begin(); it != content.end(); ++it ) {
00582           PathInfo p( path + *it, statmode );
00583           retlist.push_back( DirEntry( *it, p.fileType() ) );
00584         }
00585       }
00586 
00587       return res;
00588     }
00589 
00591     //
00592     //  METHOD NAME : is_empty_dir
00593     //  METHOD TYPE : int
00594     //
00595     int is_empty_dir(const Pathname & path)
00596     {
00597       DIR * dir = ::opendir( path.asString().c_str() );
00598       if ( ! dir ) {
00599         return _Log_Result( errno );
00600       }
00601 
00602       struct dirent *entry;
00603       while ( (entry = ::readdir( dir )) != NULL )
00604       {
00605         std::string name(entry->d_name);
00606 
00607         if ( name == "." || name == "..")
00608           continue;
00609 
00610         break;
00611       }
00612       ::closedir( dir );
00613 
00614       return entry != NULL ? -1 : 0;
00615     }
00616 
00618     //
00619     //  METHOD NAME : unlink
00620     //  METHOD TYPE : int
00621     //
00622     int unlink( const Pathname & path )
00623     {
00624       DBG << "unlink " << path;
00625       if ( ::unlink( path.asString().c_str() ) == -1 ) {
00626         return _Log_Result( errno );
00627       }
00628       return _Log_Result( 0 );
00629     }
00630 
00632     //
00633     //  METHOD NAME : rename
00634     //  METHOD TYPE : int
00635     //
00636     int rename( const Pathname & oldpath, const Pathname & newpath )
00637     {
00638       DBG << "rename " << oldpath << " -> " << newpath;
00639       if ( ::rename( oldpath.asString().c_str(), newpath.asString().c_str() ) == -1 ) {
00640         return _Log_Result( errno );
00641       }
00642       return _Log_Result( 0 );
00643     }
00644 
00646     //
00647     //  METHOD NAME : copy
00648     //  METHOD TYPE : int
00649     //
00650     int copy( const Pathname & file, const Pathname & dest )
00651     {
00652       DBG << "copy " << file << " -> " << dest << ' ';
00653 
00654       PathInfo sp( file );
00655       if ( !sp.isFile() ) {
00656         return _Log_Result( EINVAL );
00657       }
00658 
00659       PathInfo dp( dest );
00660       if ( dp.isDir() ) {
00661         return _Log_Result( EISDIR );
00662       }
00663 
00664       const char *const argv[] = {
00665         "/bin/cp",
00666         "--",
00667         file.asString().c_str(),
00668         dest.asString().c_str(),
00669         NULL
00670       };
00671       ExternalProgram prog( argv, ExternalProgram::Stderr_To_Stdout );
00672       for ( string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
00673         DBG << "  " << output;
00674       }
00675       int ret = prog.close();
00676       return _Log_Result( ret, "returned" );
00677     }
00678 
00680     //
00681     //  METHOD NAME : symlink
00682     //  METHOD TYPE : int
00683     //
00684     int symlink( const Pathname & oldpath, const Pathname & newpath )
00685     {
00686       DBG << "symlink " << newpath << " -> " << oldpath;
00687       if ( ::symlink( oldpath.asString().c_str(), newpath.asString().c_str() ) == -1 ) {
00688         return _Log_Result( errno );
00689       }
00690       return _Log_Result( 0 );
00691     }
00692 
00694     //
00695     //  METHOD NAME : hardlink
00696     //  METHOD TYPE : int
00697     //
00698     int hardlink( const Pathname & oldpath, const Pathname & newpath )
00699     {
00700       DBG << "hardlink " << newpath << " -> " << oldpath;
00701       if ( ::link( oldpath.asString().c_str(), newpath.asString().c_str() ) == -1 ) {
00702         return _Log_Result( errno );
00703       }
00704       return _Log_Result( 0 );
00705     }
00706 
00708     //
00709     //  METHOD NAME : copy_file2dir
00710     //  METHOD TYPE : int
00711     //
00712     int copy_file2dir( const Pathname & file, const Pathname & dest )
00713     {
00714       DBG << "copy_file2dir " << file << " -> " << dest << ' ';
00715 
00716       PathInfo sp( file );
00717       if ( !sp.isFile() ) {
00718         return _Log_Result( EINVAL );
00719       }
00720 
00721       PathInfo dp( dest );
00722       if ( !dp.isDir() ) {
00723         return _Log_Result( ENOTDIR );
00724       }
00725 
00726       const char *const argv[] = {
00727         "/bin/cp",
00728         "--",
00729         file.asString().c_str(),
00730         dest.asString().c_str(),
00731         NULL
00732       };
00733       ExternalProgram prog( argv, ExternalProgram::Stderr_To_Stdout );
00734       for ( string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
00735         DBG << "  " << output;
00736       }
00737       int ret = prog.close();
00738       return _Log_Result( ret, "returned" );
00739     }
00740 
00742     //
00743     //  METHOD NAME : md5sum
00744     //  METHOD TYPE : std::string
00745     //
00746     std::string md5sum( const Pathname & file )
00747     {
00748       if ( ! PathInfo( file ).isFile() ) {
00749         return string();
00750       }
00751       std::ifstream istr( file.asString().c_str() );
00752       if ( ! istr ) {
00753         return string();
00754       }
00755       return Digest::digest( "MD5", istr );
00756     }
00757 
00759     //
00760     //  METHOD NAME : sha1sum
00761     //  METHOD TYPE : std::string
00762     //
00763     std::string sha1sum( const Pathname & file )
00764     {
00765       if ( ! PathInfo( file ).isFile() ) {
00766         return string();
00767       }
00768       std::ifstream istr( file.asString().c_str() );
00769       if ( ! istr ) {
00770         return string();
00771       }
00772       return Digest::digest( "SHA1", istr );
00773     }
00774 
00776     //
00777     //  METHOD NAME : erase
00778     //  METHOD TYPE : int
00779     //
00780     int erase( const Pathname & path )
00781     {
00782       int res = 0;
00783       PathInfo p( path, PathInfo::LSTAT );
00784       if ( p.isExist() )
00785         {
00786           if ( p.isDir() )
00787             res = recursive_rmdir( path );
00788           else
00789             res = unlink( path );
00790         }
00791       return res;
00792     }
00793 
00795     //
00796     //  METHOD NAME : chmod
00797     //  METHOD TYPE : int
00798     //
00799     int chmod( const Pathname & path, mode_t mode )
00800     {
00801       DBG << "chmod " << path << ' ' << str::octstring( mode );
00802       if ( ::chmod( path.asString().c_str(), mode ) == -1 ) {
00803         return _Log_Result( errno );
00804       }
00805       return _Log_Result( 0 );
00806     }
00807 
00809     //
00810     //  METHOD NAME : zipType
00811     //  METHOD TYPE : ZIP_TYPE
00812     //
00813     ZIP_TYPE zipType( const Pathname & file )
00814     {
00815       ZIP_TYPE ret = ZT_NONE;
00816 
00817       int fd = open( file.asString().c_str(), O_RDONLY );
00818 
00819       if ( fd != -1 ) {
00820         const int magicSize = 3;
00821         unsigned char magic[magicSize];
00822         memset( magic, 0, magicSize );
00823         if ( read( fd, magic, magicSize ) == magicSize ) {
00824           if ( magic[0] == 0037 && magic[1] == 0213 ) {
00825             ret = ZT_GZ;
00826           } else if ( magic[0] == 'B' && magic[1] == 'Z' && magic[2] == 'h' ) {
00827             ret = ZT_BZ2;
00828           }
00829         }
00830         close( fd );
00831       }
00832 
00833       return ret;
00834     }
00835 
00837   } // namespace filesystem
00840 } // namespace zypp

Generated on Thu Jul 6 00:07:22 2006 for zypp by  doxygen 1.4.6