// -*- C++ -*- (c) 2015 Jiří Weiser #include "fs-manager.h" namespace divine { namespace fs { Manager::Manager( bool ) : _root{ std::allocate_shared< INode >( memory::AllocatorPure(), Mode::DIR | Mode::RWXUSER | Mode::RWXGROUP | Mode::RWXOTHER ) }, _currentDirectory{ _root }, _standardIO{ { std::allocate_shared< INode >( memory::AllocatorPure(), Mode::FILE | Mode::RUSER ), std::allocate_shared< INode >( memory::AllocatorPure(), Mode::FILE | Mode::RUSER ) } }, _openFD{ std::allocate_shared< FileDescriptor >( memory::AllocatorPure(), _standardIO[ 0 ], flags::Open::Read ),// stdin std::allocate_shared< FileDescriptor >( memory::AllocatorPure(), _standardIO[ 1 ], flags::Open::Write ),// stdout std::allocate_shared< FileDescriptor >( memory::AllocatorPure(), _standardIO[ 1 ], flags::Open::Write )// stderr }, _umask{ Mode::WGROUP | Mode::WOTHER } { _root->assign( new( memory::nofail ) Directory( _root ) ); _standardIO[ 1 ]->assign( new( memory::nofail ) WriteOnlyFile() ); } void Manager::createDirectoryAt( int dirfd, utils::String name, mode_t mode ) { if ( name.empty() ) throw Error( ENOENT ); WeakNode savedDir = _currentDirectory; auto d = utils::make_defer( [&]{ _currentDirectory = savedDir; } ); if ( path::isRelative( name ) && dirfd != CURRENT_DIRECTORY ) changeDirectory( dirfd ); Node current; std::tie( current, name ) = _findDirectoryOfFile( name ); _checkGrants( current, Mode::WUSER ); Directory *dir = current->data()->as< Directory >(); mode &= ~umask() & ( Mode::RWXUSER | Mode::RWXGROUP | Mode::RWXOTHER ); mode |= Mode::DIR; if ( current->mode().hasGUID() ) mode |= Mode::GUID; Node node = std::allocate_shared< INode >( memory::AllocatorPure(), mode ); node->assign( new( memory::nofail ) Directory( node, current ) ); dir->create( std::move( name ), node ); } void Manager::createHardLinkAt( int newdirfd, utils::String name, int olddirfd, const utils::String &target, Flags< flags::At > fl ) { if ( name.empty() || target.empty() ) throw Error( ENOENT ); if ( fl.has( flags::At::Invalid ) ) throw Error( EINVAL ); Node current; { WeakNode savedDir = _currentDirectory; auto d = utils::make_defer( [&]{ _currentDirectory = savedDir; } ); if ( path::isRelative( name ) && newdirfd != CURRENT_DIRECTORY ) changeDirectory( newdirfd ); std::tie( current, name )= _findDirectoryOfFile( name ); } _checkGrants( current, Mode::WUSER ); Directory *dir = current->data()->as< Directory >(); Node targetNode; { WeakNode savedDir = _currentDirectory; auto d = utils::make_defer( [&]{ _currentDirectory = savedDir; } ); if ( path::isRelative( target ) && olddirfd != CURRENT_DIRECTORY ) changeDirectory( olddirfd ); targetNode = findDirectoryItem( target, fl.has( flags::At::SymFollow ) ); } if ( !targetNode ) throw Error( ENOENT ); if ( targetNode->mode().isDirectory() ) throw Error( EPERM ); dir->create( std::move( name ), targetNode ); } void Manager::createSymLinkAt( int dirfd, utils::String name, utils::String target ) { if ( name.empty() ) throw Error( ENOENT ); if ( target.size() > PATH_LIMIT ) throw Error( ENAMETOOLONG ); WeakNode savedDir = _currentDirectory; auto d = utils::make_defer( [&]{ _currentDirectory = savedDir; } ); if ( path::isRelative( name ) && dirfd != CURRENT_DIRECTORY ) changeDirectory( dirfd ); Node current; std::tie( current, name ) = _findDirectoryOfFile( name ); _checkGrants( current, Mode::WUSER ); Directory *dir = current->data()->as< Directory >(); mode_t mode = 0; mode |= Mode::RWXUSER | Mode::RWXGROUP | Mode::RWXOTHER; mode |= Mode::LINK; Node node = std::allocate_shared< INode >( memory::AllocatorPure(), mode ); node->assign( new( memory::nofail ) Link( std::move( target ) ) ); dir->create( std::move( name ), node ); } void Manager::createFifoAt( int dirfd, utils::String name, mode_t mode ) { if ( name.empty() ) throw Error( ENOENT ); WeakNode savedDir = _currentDirectory; auto d = utils::make_defer( [&]{ _currentDirectory = savedDir; } ); if ( path::isRelative( name ) && dirfd != CURRENT_DIRECTORY ) changeDirectory( dirfd ); Node current; std::tie( current, name ) = _findDirectoryOfFile( name ); _checkGrants( current, Mode::WUSER ); Directory *dir = current->data()->as< Directory >(); mode &= ~umask() & ( Mode::RWXUSER | Mode::RWXGROUP | Mode::RWXOTHER ); mode |= Mode::FIFO; Node node = std::allocate_shared< INode >( memory::AllocatorPure(), mode ); node->assign( new( memory::nofail ) Pipe() ); dir->create( std::move( name ), node ); } ssize_t Manager::readLinkAt( int dirfd, utils::String name, char *buf, size_t count ) { WeakNode savedDir = _currentDirectory; auto d = utils::make_defer( [&]{ _currentDirectory = savedDir; } ); if ( path::isRelative( name ) && dirfd != CURRENT_DIRECTORY ) changeDirectory( dirfd ); Node inode = findDirectoryItem( std::move( name ), false ); if ( !inode ) throw Error( ENOENT ); if ( !inode->mode().isLink() ) throw Error( EINVAL ); Link *sl = inode->data()->as< Link >(); const utils::String &target = sl->target(); auto realLength = std::min( target.size(), count ); std::copy( target.c_str(), target.c_str() + realLength, buf ); return realLength; } void Manager::accessAt( int dirfd, utils::String name, Flags< flags::Access > mode, Flags< flags::At > fl ) { if ( name.empty() ) throw Error( ENOENT ); if ( mode.has( flags::Access::Invalid ) || fl.has( flags::At::Invalid ) ) throw Error( EINVAL ); WeakNode savedDir = _currentDirectory; auto d = utils::make_defer( [&]{ _currentDirectory = savedDir; } ); if ( path::isRelative( name ) && dirfd != CURRENT_DIRECTORY ) changeDirectory( dirfd ); Node item = findDirectoryItem( name, !fl.has( flags::At::SymNofollow ) ); if ( !item ) throw Error( EACCES ); if ( ( mode.has( flags::Access::Read ) && !item->mode().userRead() ) || ( mode.has( flags::Access::Write ) && !item->mode().userWrite() ) || ( mode.has( flags::Access::Execute ) && !item->mode().userExecute() ) ) throw Error( EACCES ); } int Manager::openFileAt( int dirfd, utils::String name, Flags< flags::Open > fl, mode_t mode ) { WeakNode savedDir = _currentDirectory; auto d = utils::make_defer( [&]{ _currentDirectory = savedDir; } ); if ( path::isRelative( name ) && dirfd != CURRENT_DIRECTORY ) changeDirectory( dirfd ); Node file = findDirectoryItem( name, !fl.has( flags::Open::SymNofollow ) ); if ( fl.has( flags::Open::Create ) ) { if ( file ) { if ( fl.has( flags::Open::Excl ) ) throw Error( EEXIST ); } else { _createFile( std::move( name ), mode, &file ); } } else if ( !file ) throw Error( ENOENT ); if ( fl.has( flags::Open::Read ) ) _checkGrants( file, Mode::RUSER ); if ( fl.has( flags::Open::Write ) ) { _checkGrants( file, Mode::WUSER ); if ( file->mode().isDirectory() ) throw Error( EISDIR ); if ( fl.has( flags::Open::Truncate ) ) if ( auto f = file->data()->as< File >() ) f->clear(); } if ( fl.has( flags::Open::NoAccess ) ) { fl.clear( flags::Open::Read ); fl.clear( flags::Open::Write ); } if ( file->mode().isFifo() ) return _getFileDescriptor( std::allocate_shared< PipeDescriptor >( memory::AllocatorPure(), file, fl, true ) ); return _getFileDescriptor( std::allocate_shared< FileDescriptor >( memory::AllocatorPure(), file, fl ) ); } void Manager::closeFile( int fd ) { getFile( fd ).reset(); } int Manager::duplicate( int oldfd ) { return _getFileDescriptor( getFile( oldfd ) ); } int Manager::duplicate2( int oldfd, int newfd ) { if ( oldfd == newfd ) return newfd; auto f = getFile( oldfd ); if ( newfd < 0 || newfd > FILE_DESCRIPTOR_LIMIT ) throw Error( EBADF ); if ( newfd >= _openFD.size() ) _openFD.resize( newfd + 1 ); _openFD[ newfd ] = f; return newfd; } std::shared_ptr< FileDescriptor > &Manager::getFile( int fd ) { if ( fd >= 0 && fd < _openFD.size() && _openFD[ fd ] ) return _openFD[ fd ]; throw Error( EBADF ); } std::pair< int, int > Manager::pipe() { mode_t mode = Mode::RWXUSER | Mode::FIFO; Node node = std::allocate_shared< INode >( memory::AllocatorPure(), mode ); node->assign( new( memory::nofail ) Pipe( true, true ) ); return { _getFileDescriptor( std::allocate_shared< PipeDescriptor >( memory::AllocatorPure(), node, flags::Open::Read ) ), _getFileDescriptor( std::allocate_shared< PipeDescriptor >( memory::AllocatorPure(), node, flags::Open::Write ) ) }; } void Manager::removeFile( utils::String name ) { if ( name.empty() ) throw Error( ENOENT ); Node current; std::tie( current, name ) = _findDirectoryOfFile( name ); _checkGrants( current, Mode::WUSER ); Directory *dir = current->data()->as< Directory >(); dir->remove( name ); } void Manager::removeDirectory( utils::String name ) { if ( name.empty() ) throw Error( ENOENT ); Node current; std::tie( current, name ) = _findDirectoryOfFile( name ); _checkGrants( current, Mode::WUSER ); Directory *dir = current->data()->as< Directory >(); dir->removeDirectory( name ); } void Manager::removeAt( int dirfd, utils::String name, flags::At fl ) { WeakNode savedDir = _currentDirectory; auto d = utils::make_defer( [&]{ _currentDirectory = savedDir; } ); if ( path::isRelative( name ) && dirfd != CURRENT_DIRECTORY ) changeDirectory( dirfd ); switch( fl ) { case flags::At::NoFlags: removeFile( name ); break; case flags::At::RemoveDir: removeDirectory( name ); break; default: throw Error( EINVAL ); } } void Manager::renameAt( int newdirfd, utils::String newpath, int olddirfd, utils::String oldpath ) { Node oldNode; Directory *oldNodeDirectory; Node newNode; Directory *newNodeDirectory; utils::String oldName; utils::String newName; { WeakNode savedDir = _currentDirectory; auto d = utils::make_defer( [&]{ _currentDirectory = savedDir; } ); if ( path::isRelative( oldpath ) && olddirfd != CURRENT_DIRECTORY ) changeDirectory( olddirfd ); std::tie( oldNode, oldName ) = _findDirectoryOfFile( oldpath ); _checkGrants( oldNode, Mode::WUSER ); oldNodeDirectory = oldNode->data()->as< Directory >(); } oldNode = oldNodeDirectory->find( oldName ); if ( !oldNode ) throw Error( ENOENT ); { WeakNode savedDir = _currentDirectory; auto d = utils::make_defer( [&]{ _currentDirectory = savedDir; } ); if ( path::isRelative( newpath ) && newdirfd != CURRENT_DIRECTORY ) changeDirectory( newdirfd ); newNode = _findDirectoryItem( newpath, false, [&]( Node n ) { if ( n == oldNode ) throw Error( EINVAL ); } ); } if ( !newNode ) { std::tie( newNode, newName ) = _findDirectoryOfFile( newpath ); _checkGrants( newNode, Mode::WUSER ); newNodeDirectory = newNode->data()->as< Directory >(); newNodeDirectory->create( std::move( newName ), oldNode ); } else { if ( oldNode->mode().isDirectory() ) { if ( !newNode->mode().isDirectory() ) throw Error( ENOTDIR ); if ( newNode->size() > 2 ) throw Error( ENOTEMPTY ); } else if ( newNode->mode().isDirectory() ) throw Error( EISDIR ); newNodeDirectory->replaceEntry( newName, oldNode ); } oldNodeDirectory->forceRemove( oldName ); } off_t Manager::lseek( int fd, off_t offset, Seek whence ) { auto f = getFile( fd ); if ( f->inode()->mode().isFifo() ) throw Error( ESPIPE ); switch( whence ) { case Seek::Set: if ( offset < 0 ) throw Error( EINVAL ); f->offset( offset ); break; case Seek::Current: if ( std::numeric_limits< size_t >::max() - f->offset() < offset ) throw Error( EOVERFLOW ); if ( offset < f->offset() ) throw Error( EINVAL ); f->offset( offset + f->offset() ); break; case Seek::End: if ( std::numeric_limits< size_t >::max() - f->size() < offset ) throw Error( EOVERFLOW ); if ( f->size() < -offset ) throw Error( EINVAL ); f->offset( f->size() + offset ); break; default: throw Error( EINVAL ); } return f->offset(); } void Manager::truncate( Node inode, off_t length ) { if ( inode->mode().isDirectory() ) throw Error( EISDIR ); if ( !inode->mode().isFile() ) throw Error( EINVAL ); _checkGrants( inode, Mode::WUSER ); RegularFile *f = inode->data()->as< RegularFile >(); f->resize( length ); } void Manager::changeDirectory( utils::String pathname ) { Node item = findDirectoryItem( pathname ); if ( !item ) throw Error( ENOENT ); if ( !item->mode().isDirectory() ) throw Error( ENOTDIR ); _currentDirectory = item; } void Manager::changeDirectory( int dirfd ) { Node item = getFile( dirfd )->inode(); _checkGrants( item, Mode::XUSER ); if ( !item ) throw Error( ENOENT ); if ( !item->mode().isDirectory() ) throw Error( ENOTDIR ); _currentDirectory = item; } void Manager::chmodAt( int dirfd, utils::String name, mode_t mode, Flags< flags::At > fl ) { if ( fl.has( flags::At::Invalid ) ) throw Error( EINVAL ); WeakNode savedDir = _currentDirectory; auto d = utils::make_defer( [&]{ _currentDirectory = savedDir; } ); if ( path::isRelative( name ) && dirfd != CURRENT_DIRECTORY ) changeDirectory( dirfd ); Node inode = findDirectoryItem( std::move( name ), !fl.has( flags::At::SymNofollow ) ); _chmod( inode, mode ); } void Manager::chmod( int fd, mode_t mode ) { _chmod( getFile( fd )->inode(), mode ); } DirectoryDescriptor *Manager::openDirectory( int fd ) { Node inode = getFile( fd )->inode(); _checkGrants( inode, Mode::RUSER | Mode::XUSER ); _openDD.emplace_back( inode, fd ); return &_openDD.back(); } DirectoryDescriptor *Manager::getDirectory( void *descriptor ) { for ( auto i = _openDD.begin(); i != _openDD.end(); ++i ) { if ( &*i == descriptor ) { return &*i; } } throw Error( EBADF ); } void Manager::closeDirectory( void *descriptor ) { for ( auto i = _openDD.begin(); i != _openDD.end(); ++i ) { if ( &*i == descriptor ) { closeFile( i->fd() ); _openDD.erase( i ); return; } } throw Error( EBADF ); } template< typename... Args > void Manager::_createFile( utils::String name, mode_t mode, Node *file, Args &&... args ) { if ( name.empty() ) throw Error( ENOENT ); Node current; std::tie( current, name ) = _findDirectoryOfFile( name ); _checkGrants( current, Mode::WUSER ); Directory *dir = current->data()->as< Directory >(); mode &= ~umask() & ( Mode::RWXUSER | Mode::RWXGROUP | Mode::RWXOTHER ); mode |= Mode::FILE; Node node = std::allocate_shared< INode >( memory::AllocatorPure(), mode ); node->assign( new( memory::nofail ) RegularFile( std::forward< Args >( args )... ) ); dir->create( std::move( name ), node ); if ( file ) *file = node; } Node Manager::findDirectoryItem( utils::String name, bool followSymLinks ) { return _findDirectoryItem( std::move( name ), followSymLinks, []( Node ){} ); } template< typename I > Node Manager::_findDirectoryItem( utils::String name, bool followSymLinks, I itemChecker ) { if ( name.size() > PATH_LIMIT ) throw Error( ENAMETOOLONG ); name = path::normalize( name ); Node current = _root; if ( path::isRelative( name ) ) current = currentDirectory(); Node item = current; utils::Queue< utils::String > q( path::splitPath< utils::Deque< utils::String > >( name ) ); utils::Set< Link * > loopDetector; while ( !q.empty() ) { if ( !current->mode().isDirectory() ) throw Error( ENOTDIR ); _checkGrants( current, Mode::XUSER ); Directory *dir = current->data()->as< Directory >(); { auto d = utils::make_defer( [&]{ q.pop(); } ); const auto &subFolder = q.front(); if ( subFolder.empty() ) continue; if ( subFolder.size() > FILE_NAME_LIMIT ) throw Error( ENAMETOOLONG ); item = dir->find( subFolder ); } if ( !item ) { if ( q.empty() ) return nullptr; throw Error( ENOENT ); } itemChecker( item ); if ( item->mode().isDirectory() ) current = item; else if ( item->mode().isLink() && ( followSymLinks || !q.empty() ) ) { Link *sl = item->data()->as< Link >(); if ( !loopDetector.insert( sl ).second ) throw Error( ELOOP ); utils::Queue< utils::String > _q( path::splitPath< utils::Deque< utils::String > >( sl->target() ) ); while ( !q.empty() ) { _q.emplace( std::move( q.front() ) ); q.pop(); } q.swap( _q ); if ( path::isAbsolute( sl->target() ) ) current = _root; continue; } else { if ( q.empty() ) break; throw Error( ENOTDIR ); } } return item; } std::pair< Node, utils::String > Manager::_findDirectoryOfFile( utils::String name ) { name = path::normalize( name ); if ( name.size() > PATH_LIMIT ) throw Error( ENAMETOOLONG ); utils::String pathname; std::tie( pathname, name ) = path::splitFileName( name ); Node item = findDirectoryItem( pathname ); if ( !item ) throw Error( ENOENT ); _checkGrants( item, Mode::XUSER ); if ( !item->mode().isDirectory() ) throw Error( ENOTDIR ); return { item, name }; } int Manager::_getFileDescriptor( std::shared_ptr< FileDescriptor > f ) { int i = 0; for ( auto &fd : _openFD ) { if ( !fd ) { fd = f; return i; } ++i; } if ( _openFD.size() >= FILE_DESCRIPTOR_LIMIT ) throw Error( ENFILE ); _openFD.push_back( f ); return i; } void Manager::_insertSnapshotItem( const SnapshotFS &item ) { switch( item.type ) { case Type::File: _createFile( item.name, item.mode, nullptr, item.content, item.length ); break; case Type::Directory: createDirectoryAt( CURRENT_DIRECTORY, item.name, item.mode ); break; case Type::SymLink: createSymLinkAt( CURRENT_DIRECTORY, item.name, item.content ); break; default: break; } } void Manager::_checkGrants( Node inode, mode_t grant ) const { if ( ( inode->mode() & grant ) != grant ) throw Error( EACCES ); } void Manager::_chmod( Node inode, mode_t mode ) { inode->mode() = ( inode->mode() & ~Mode::CHMOD ) | ( mode & Mode::CHMOD ); } } // namespace fs } // namespace divine