Browse Source

HV: * drag in some more jive5ab code [because it's soooooo good :-)]

* added libvbs.{h,cc} - a small library to open/read VBS recordings
      as a simple file

git-svn-id: svn+ssh://code.jive.eu/code/svn/vbs_fs@5 5b2df0cb-e17b-44c8-90c8-480d528b6e0d
master
JIVE Software Dev 4 years ago
parent
commit
b7ed0c9bf8
8 changed files with 1512 additions and 17 deletions
  1. +9
    -2
      Makefile
  2. +47
    -0
      auto_array.h
  3. +55
    -0
      dosyscall.cc
  4. +177
    -0
      dosyscall.h
  5. +1
    -1
      evlbidebug.cc
  6. +679
    -0
      libvbs.cc
  7. +33
    -0
      libvbs.h
  8. +511
    -14
      vbs_fs.cc

+ 9
- 2
Makefile View File

@@ -1,13 +1,20 @@
CC=gcc
CXX=g++
INCS=-I.
CXXOPT=-g -fPIC $(OPT) -Wall -W -Werror -Wextra -pedantic -D_REENTRANT -D_POSIX_PTHREAD_SEMANTICS -D__STDC_FORMAT_MACROS -Wcast-qual -Wwrite-strings -Wredundant-decls -Wfloat-equal -Wshadow -D_FILE_OFFSET_BITS=64
CXXOPT=-g -fPIC $(OPT) -m64 -Wall -W -Werror -Wextra -pedantic -D_REENTRANT -D_POSIX_PTHREAD_SEMANTICS -D__STDC_FORMAT_MACROS -Wcast-qual -Wwrite-strings -Wredundant-decls -Wfloat-equal -Wshadow -D_FILE_OFFSET_BITS=64

OBJS=evlbidebug.o regular_expression.o
OBJS=evlbidebug.o regular_expression.o dosyscall.o


vbs_fs: vbs_fs.cc $(OBJS)
$(CXX) $(INCS) $(CXXOPT) -o vbs_fs vbs_fs.cc $(OBJS) -lpthread -lfuse

libvbs.so: libvbs.cc $(OBJS)
$(CXX) -shared -Bdynamic $(CXXOPT) $(INCS) -o libvbs.so libvbs.cc $(OBJS) -lpthread

tvbs: tvbs.c libvbs.so
$(CC) -g -fPIC -m64 $(INCS) -L. -o tvbs tvbs.c -lvbs

%.o: %.cc
$(CXX) $(INCS) -c $(CXXOPT) -o $@ $<



+ 47
- 0
auto_array.h View File

@@ -0,0 +1,47 @@
#ifndef AUTO_ARRAY_H
#define AUTO_ARRAY_H

#include <sys/types.h>

template <typename T>
struct auto_array {
public:
typedef T element_type;

auto_array() :
m_ptr( 0 )
{}

auto_array(T* const p):
m_ptr( p )
{}

auto_array(const auto_array<T>& other):
m_ptr( other.m_ptr )
{ other.m_ptr = 0; }

auto_array<T>& operator=(const auto_array<T>& other) {
if( this!=&other ) {
m_ptr = other.m_ptr;
other.m_ptr = 0;
}
return *this;
}

T& operator[](size_t idx) {
return m_ptr[idx];
}
T const& operator[](size_t idx) const {
return m_ptr[idx];
}

~auto_array() {
delete [] m_ptr;
}

private:
element_type* m_ptr;
};


#endif

+ 55
- 0
dosyscall.cc View File

@@ -0,0 +1,55 @@
// implementation
// Copyright (C) 2007-2008 Harro Verkouter
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// Author: Harro Verkouter - verkouter@jive.nl
// Joint Institute for VLBI in Europe
// P.O. Box 2
// 7990 AA Dwingeloo
#include <dosyscall.h>

#include <errno.h>
#include <string.h>


using namespace std;

//
// The 'last system-error type'
//
lastsyserror_type::lastsyserror_type():
sys_errno( errno ), sys_errormessage( ((sys_errno!=0)?(::strerror(sys_errno)):("<success>")) )
{}

// if errno == 0, don't show anything
ostream& operator<<( ostream& os, const lastsyserror_type& lse ) {
if( lse.sys_errno!=0 )
os << " - " << lse.sys_errormessage << "(" << lse.sys_errno << ")";
return os;
}

//
// the exception
//
syscallexception::syscallexception( const string& s ):
msg( s )
{}

const char* syscallexception::what() const throw() {
return msg.c_str();
}
syscallexception::~syscallexception() throw()
{}



+ 177
- 0
dosyscall.h View File

@@ -0,0 +1,177 @@
// 'wrapper' around calling systemcalls.
// Copyright (C) 2007-2008 Harro Verkouter
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// Author: Harro Verkouter - verkouter@jive.nl
// Joint Institute for VLBI in Europe
// P.O. Box 2
// 7990 AA Dwingeloo
//
// In case of a failed condition, it will throw and will
// automatically add location details as well as the call that failed and
// the system error message (as indicated by errno).
//
// Defines:
// ASSERT_<cond>( <expression> )
// assertion macros which will throw if
// the <expression> does not meet <condition>
// ASSERT2_<cond>( <expression>, <cleanupcode> )
// id. as above but will execute <cleanupcode> immediately
// before throwing
#ifndef EVLBI5A_DOSYSCALL_H
#define EVLBI5A_DOSYSCALL_H

#include <iostream>
#include <sstream>
#include <string>
#include <exception>


// the constructor of this baby captures 'errno' and the associated message
struct lastsyserror_type {
lastsyserror_type();

int sys_errno;
std::string sys_errormessage;
};

// an exception of this type is thrown
struct syscallexception :
public std::exception
{
syscallexception( const std::string& s );

virtual const char* what() const throw();
virtual ~syscallexception() throw();

const std::string msg;
};

// if errno == 0, don't show anything
std::ostream& operator<<( std::ostream& os, const lastsyserror_type& lse );



// Set up the defines to make it all readable (ahem) and usable
#define SYSCALLLOCATION \
std::string fn_( __FILE__); int ln_(__LINE__);

#define SYSCALLSTUFF(fubarvar) \
lastsyserror_type lse; std::ostringstream lclSvar_0a;\
lclSvar_0a << fn_ << "@" << ln_ << " [" << fubarvar << "] fails " << lse;

// SCINFO [short for SysCallInfo]:
// can be used to add extra info to the errormessage. Use as (one of) the
// entries in the ASSERT2_*() macros: eg:
//
// int fd;
// string proto;
//
// <... open file and store fd ...>
//
// ASSERT2_NZERO( getprotobyname(proto.c_str()), // see if this works
// ::close(fd); SCINFO("proto.c_str()="<<proto.c_str()) ); // if not, execute this
//
#define SCINFO(a) \
lclSvar_0a << a;

// If you want to save the returnvalue of the function
// that can be easily done via:
//
// int fd;
//
// ASSERT_POS( (fd=open("<some>/<path>/<to>/<file>", O_RDONLY)) );
// read(fd, <buffer>, <size>);
//
// NOTE: You should use ()'s around the assignment...


// Generic condition; if expression !(a) evaluates to true
// (ie: (a) evaluates to false, ie 'condition not met')
// then throw up
//
// The ASSERT2_* defines take *two* arguments; the 2nd expression will be evaluated/executed
// just before the exception is thrown. It is for cleanup code:
//
// int fd;
// char* sptr;
//
// // open a file
// ASSERT_POS( (fd=open("/some/file", O_RDONLY)) );
// // alloc memory, close file in case of error
// ASSERT2_NZERO( (sptr=(char*)malloc(hugeval)), close(fd) );
//
#define ASSERT2_COND(a, b) \
do {\
SYSCALLLOCATION;\
if( !(a) ) { \
SYSCALLSTUFF(#a);\
b;\
throw syscallexception( lclSvar_0a.str() ); \
} \
} while( 0 );
// w/o cleanup is just "with cleanup" where the cleanup is a nop
#define ASSERT_COND(a) \
ASSERT2_COND(a, ;)


//
// Now define shortcuts for most often used conditions
//

// For systemcalls that should return 0 on success
#define ASSERT2_ZERO(a, b) \
do {\
SYSCALLLOCATION;\
if( (a)!=0 ) { \
SYSCALLSTUFF(#a);\
b; \
throw syscallexception( lclSvar_0a.str() ); \
} \
} while( 0 );

#define ASSERT_ZERO(a) \
ASSERT2_ZERO(a, ;)

// For functions that should NOT return zero
// (note: you can also stick functions in here that
// return a pointer. Long live C++! ;))
#define ASSERT2_NZERO(a, b) \
do {\
SYSCALLLOCATION;\
if( (a)==0 ) { \
SYSCALLSTUFF(#a);\
b; \
throw syscallexception( lclSvar_0a.str() ); \
} \
} while( 0 );
#define ASSERT_NZERO(a) \
ASSERT2_NZERO(a, ;)

// functions that should not return a negative number
// Note: 0 is not an error in this case (eg semget(2))
#define ASSERT2_POS(a, b) \
do {\
SYSCALLLOCATION;\
if( (a)<0 ) { \
SYSCALLSTUFF(#a);\
b; \
throw syscallexception( lclSvar_0a.str() ); \
} \
} while( 0 );
#define ASSERT_POS(a) \
ASSERT2_POS(a, ;)


#endif // includeguard

+ 1
- 1
evlbidebug.cc View File

@@ -26,7 +26,7 @@
// for ::strerror()
#include <string.h>

static int dbglev_val = 1;
static int dbglev_val = 0;
// if msglevel>fnthres_val level => functionnames are printed in DEBUG()
static int fnthres_val = 5;
static pthread_mutex_t evlbidebug_cerr_lock = PTHREAD_MUTEX_INITIALIZER;


+ 679
- 0
libvbs.cc View File

@@ -0,0 +1,679 @@
// Project includes
#include <libvbs.h>
#include <auto_array.h>
#include <evlbidebug.h>
#include <regular_expression.h>
#include <dosyscall.h>

// Standardized C++ headers
#include <iostream>
#include <map>
#include <set>
#include <list>
#include <string>
#include <cstddef>
#include <cerrno>
#include <cstring>
//#include <csignal>
#include <cstdlib>
#include <climits> // INT_MAX

// Old-style *NIX headers
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>

using namespace std;

DECLARE_EZEXCEPT(vbs_except)
DEFINE_EZEXCEPT(vbs_except)


void scanRecording(string const& recname);
void scanRecordingMountpoint(string const& recname, string const& mp);
void scanRecordingDirectory(string const& recname, string const& dir);

///////////////////////////////////////////////
// libvbs is going to have to quite often
// grovel over directories, sometimes filtering
// entries and sometimes not.
// This set of function templates allow for flexible
// filtering (or not) of directory entries.
//
// Works on already opened DIR* or std::string (==path)
// The template parameter is the predicate.
// IF the predicate(d_entry) is true, then d_entry
// will be added to the set of entries.
//
///////////////////////////////////////////////
typedef set<string> direntries_type;

// This version will call the predicate with "entry->d_name"
// (i.e. just the name of the entry in the directory) as
// parameter because we do not know the path leading to DIR*
template <typename Predicate>
direntries_type vbs_readdir(DIR* dirp, Predicate const& pred) {
// Memory for the "dirent" struct may or may not include
// space for the "d_name[]" field. POSIX sais that it's
// almost impossible to pre-allocate the correct amount of memory
// but this is currently one of the better approximations
int eno;
const size_t entryLen = offsetof(struct dirent, d_name)+::pathconf("/", _PC_NAME_MAX) + 1;
struct dirent* entryPtr;
direntries_type rv;
auto_array<unsigned char> dirEntry( new unsigned char[entryLen] );

// Make sure it's rewound
::rewinddir( dirp );

// Check each entry
// ::readdir_r(3) returns 0 on success, entryPtr==NULL if end-of-directory reached
while( (eno=::readdir_r(dirp, (struct dirent*)&dirEntry[0], &entryPtr))==0 ) {
if( entryPtr==0 )
break;
// If predicate returns true, add current entry to result
if( pred(string(entryPtr->d_name)) ) {
EZASSERT2( rv.insert(entryPtr->d_name).second, vbs_except, EZINFO("duplicate insert - " << entryPtr->d_name) );
}
}
// Force succesfull loop ending
EZASSERT2(eno==0, vbs_except, EZINFO("readdir_r failed - " << ::strerror(eno)));

return rv;
}

// This variation, which works on a given path will call the predicate with
// the FULL path to the entry, not just the name from the directory listing!
// So it is "pred( dir + "/" + entry->d_name )"
template <typename Predicate>
direntries_type vbs_readdir(string const& dir, Predicate const& pred) {
// Memory for the "dirent" struct may or may not include
// space for the "d_name[]" field. POSIX sais that it's
// almost impossible to pre-allocate the correct amount of memory
// but this is currently one of the better approximations
int eno;
DIR* dirp;
const size_t entryLen = offsetof(struct dirent, d_name)+::pathconf("/", _PC_NAME_MAX) + 1;
struct dirent* entryPtr;
direntries_type rv;
auto_array<unsigned char> dirEntry( new unsigned char[entryLen] );

// This is a systemcall so can use ASSERT*() which will
// capture the error message from errno
//ASSERT2_NZERO(dirp = ::opendir(dir.c_str()), SCINFO(" opening '" << dir << "'"));
if( (dirp = ::opendir(dir.c_str()))==0 ) {
DEBUG(1, "vbs_readdir: failed to open dir '" << dir << "' - " << ::strerror(errno) << endl);
throw errno;
}

// Check each entry
// ::readdir_r(3) returns 0 on success, entryPtr==NULL if end-of-directory reached
while( (eno=::readdir_r(dirp, (struct dirent*)&dirEntry[0], &entryPtr))==0 ) {
if( entryPtr==0 )
break;
// If predicate returns true, add current entry to result
if( pred(dir+"/"+entryPtr->d_name) ) {
EZASSERT2( rv.insert(dir+"/"+entryPtr->d_name).second, vbs_except, EZINFO("duplicate insert - " << entryPtr->d_name) );
}
}
// Force succesfull loop ending
EZASSERT2(eno==0, vbs_except, EZINFO("readdir_r failed - " << ::strerror(eno)));
::closedir(dirp);
return rv;
}


// The ones without template just use the NoFilter predicate:
// it returns true for all paths
struct NoFilter {
bool operator()(string const&) const {
return true;
}
};

direntries_type vbs_readdir(DIR* dirp) {
return vbs_readdir(dirp, NoFilter());
}

direntries_type vbs_readdir(string const& dir) {
return vbs_readdir(dir, NoFilter());
}


/////////////////////////////////////////////////////
//
// The detected mountpoints
//
/////////////////////////////////////////////////////
typedef list<string> mountpoints_type;

static string rootDir = string();
static direntries_type mountpoints = direntries_type();

void findMountPoints( void );



// Keep a global mapping of recording => filechunks
// note that we let the filechunks be an automatically sorted container

struct filechunk_type {
// Note: no default c'tor
// construct from full path name
filechunk_type(string const& fnm):
pathToChunk( fnm ), chunkOffset( 0 )
{
// Get the chunk size
int fd = ::open( fnm.c_str(), O_RDONLY );
if( fd<0 )
throw errno;
//throw string("error opening ")+fnm+": "+string(::strerror(errno));
chunkSize = ::lseek(fd, 0, SEEK_END);
::close( fd );

// At this point we assume 'fnm' looks like
// "/path/to/file/chunk.012345678"
string::size_type dot = fnm.find_last_of('.');

if( dot==string::npos )
throw EINVAL;
//throw string("error parsing chunk name ")+fnm+": no dot found!";

// Note: we must instruct strtoul(3) to use base 10 decoding. The
// numbers start with a loooot of zeroes mostly and if you pass "0"
// as third arg to strtoul(3) "accept any base, derive from prefix"
// this throws off the automatic number-base detection
chunkNumber = (unsigned int)::strtoul(fnm.substr(dot+1).c_str(), 0, 10);
}

// Note: declare chunkOffset as mutable such that we can later, after
// the chunks have been sorted and put into a set, update the
// chunkOffset value to what it should be. [Elements in a set are const
// in order to guarantee that their sorting order is not compromised
// as you alter the element - in this case WE know that the sorting only
// depends on the value of 'chunkNumber' so we can safely edit
// chunkOffset w/o worrying about compromising the set]
string pathToChunk;
off_t chunkSize;
mutable off_t chunkOffset;
unsigned int chunkNumber;

private:
// no default c'tor!
filechunk_type();
};

// Comparison operator for filechunk_type - sort by chunkNumber exclusively!
bool operator<(filechunk_type const& l, filechunk_type const& r) {
return l.chunkNumber < r.chunkNumber;
}

typedef set<filechunk_type> filechunks_type;
typedef map<string, filechunks_type> chunkcache_type;

// Keep metadata per recording
struct metadata_type {
off_t recordingSize;
};
typedef map<string, metadata_type> metadatacache_type;

static chunkcache_type chunkCache;
static metadatacache_type metadataCache;

// Mapping of filedescriptor to open file
struct openfile_type {
off_t filePointer;
unsigned int bufSize;
off_t bufBeginByteNumber;
unsigned char* bufBytes;
filechunks_type::iterator chunkPtr;
metadatacache_type::iterator metadataPtr;

// No default c'tor!
openfile_type(metadatacache_type::iterator p):
filePointer( 0 ), bufSize( 0 ), bufBeginByteNumber( 0 ), bufBytes( 0 ), metadataPtr( p )
{
// Initialize chunk-pointer to point at first chunk
chunkPtr = chunkCache[metadataPtr->first].begin();
}

// Assure that the current chunk is in memory
// returns >0 on success (size of current chunk),
// 0 if attempt to read past end of file,
// -1 on error (and sets errno)
int readChunk( void ) {
if( chunkPtr==chunkCache[metadataPtr->first].end() )
return 0;
// Under this condition we assume the current chunk is well read
if( bufBytes && bufBeginByteNumber==chunkPtr->chunkOffset )
return (int)chunkPtr->chunkSize;
DEBUG(1, "readChunk: " << chunkPtr->pathToChunk << ", " << chunkPtr->chunkSize << "B @" << chunkPtr->chunkOffset << endl);
const size_t nBufSize = (size_t)max((off_t)bufSize, chunkPtr->chunkSize+1);
DEBUG(1, " bufBytes=" << (void*)bufBytes << ", bufBeginByteNumber=" << bufBeginByteNumber << ", nBufSize=" << nBufSize << endl);
if( (bufBytes = (unsigned char*)::realloc((void*)bufBytes, nBufSize))==0 ) {
errno = ENOMEM;
return -1;
}
// Open and read the chunk into memory
int fd = ::open(chunkPtr->pathToChunk.c_str(), O_RDONLY);
if( fd<0 )
return -1;
size_t n = chunkPtr->chunkSize;
unsigned char* p = bufBytes;
while( n ) {
ssize_t nr = ::read(fd, (void*)p, n);
if( nr<=0 )
break;
n -= (size_t)nr;
p += nr;
}
// If we did not completely
int oerr = errno;
::close( fd );
if( n ) {
errno = oerr;
return -1;
}
bufSize = nBufSize;
bufBeginByteNumber = chunkPtr->chunkOffset;
return chunkPtr->chunkSize;
}
int nextChunk( void ) {
if( chunkPtr!=chunkCache[metadataPtr->first].end() )
chunkPtr++;
return readChunk();
}

private:
openfile_type();
};
typedef map<int, openfile_type> openedfiles_type;
openedfiles_type openedFiles;


// Upon shutdown, the library will close all open files
struct cleanup_type {
cleanup_type() {
// here we could do initialization
}

~cleanup_type() {
// Before clearing the caches, do clear the open files
for(openedfiles_type::const_iterator fptr=openedFiles.begin(); fptr!=openedFiles.end(); fptr++)
DEBUG(-1, "closing recording " << fptr->second.metadataPtr->first << endl);

openedFiles.clear();
chunkCache.clear();
metadataCache.clear();
}
};
static cleanup_type cleanup = cleanup_type();



//////////////////////////////////////////
//
// int vbs_init()
//
// Verify that the current root dir
// exists and that we have sufficient
// privileges to look inside it.
//
// Return 0 on success, -1 on error and
// sets errno.
//
// EBUSY if there are currently files
// still open
//
/////////////////////////////////////////

int vbs_init( char const* rootdir ) {
if( !openedFiles.empty() ) {
errno = EBUSY;
return -1;
}

// Set new root dir
struct stat status;

// Propagate failure to stat the rootdir
if( ::lstat(rootdir, &status)<0 )
return -1;
// Verify that it is a DIR that we have permission to enter into and
// read
if( !S_ISDIR(status.st_mode) ) {
errno = ENOTDIR;
return -1;
}
if( (status.st_mode & S_IRUSR)==0 ||
(status.st_mode & S_IXUSR)==0 ) {
errno = EPERM;
return -1;
}

// clear all caches and really set rootDir
mountpoints.clear();
chunkCache.clear();
metadataCache.clear();

rootDir = string(rootdir);
return 0;
}


//////////////////////////////////
//
// vbs_open(char const* nm)
//
// Scan mountpoints under rootDir
// for subdirectories named 'nm'
// and scan those for fileChunks
//
//////////////////////////////////

int vbs_open(char const* recname) {
int rv = -1;
try {
findMountPoints();
// Scan current mount points for files for recording 'recname'
scanRecording( recname );
// If recname not in metadata cache ... bummer!
metadatacache_type::iterator metadataPtr = metadataCache.find(recname);
if( metadataPtr==metadataCache.end() )
throw ENOENT;
// Okiedokie, the recording exists. Allocate a new filedescriptor
// and put it in the opened files thingy
rv = INT_MAX - openedFiles.size();
openedFiles.insert( make_pair(rv, openfile_type(metadataPtr)) );
}
catch( int eno ) {
errno = eno;
}
catch( exception const& e ) {
DEBUG(1, "vbs_open: " << e.what() << endl);
errno = EINVAL;
}
return rv;
}

//////////////////////////////////
//
// int vbs_close(int fd)
//
// close a previously opened
// recording
//
//////////////////////////////////

ssize_t vbs_read(int fd, void* buf, size_t count) {
unsigned char* bufc = (unsigned char*)buf;
openedfiles_type::iterator fptr = openedFiles.find(fd);

if( fptr==openedFiles.end() ) {
errno = EBADF;
return -1;
}
// Read bytes from file!
int r;
openfile_type& of = fptr->second;

// filePointer < recordingSize, thus reading is possible.
if( (r=of.readChunk())<=0 )
return r;

// The block of memory we want to read is:
// of.filePointer -> of.filePointer + count
size_t nr = count;
// whilst there are bytes to read ...
while( nr ) {
const filechunk_type& chunk = *fptr->second.chunkPtr;
off_t n2r = min((off_t)nr, chunk.chunkOffset+chunk.chunkSize - of.filePointer);

// If we've exhausted the current block, move on to the next block
// and try again
if( n2r<=0 ) {
if( (r=of.nextChunk())<=0 )
break;
continue;
}
// Now we can read n2r bytes into the user's buffer
::memcpy((void*)bufc, (void*)(of.bufBytes + (of.filePointer - chunk.chunkOffset)), (size_t)n2r);
// Increase file pointer
of.filePointer += n2r;
nr -= (size_t)n2r;
}
// If this condition holds, the reading of a chunk failed and errno
// indicates why it has failed.
if( r<0 )
return -1;
return (ssize_t)(count - nr);
}

//////////////////////////////////////////////////
//
// int vbs_lseek(int fd, off_t offset, int whence)
//
// see lseek(2)
//
/////////////////////////////////////////////////

off_t vbs_lseek(int fd, off_t offset, int whence) {
off_t newfp;
openedfiles_type::iterator fptr = openedFiles.find(fd);

if( fptr==openedFiles.end() ) {
errno = EBADF;
return -1;
}

openfile_type& of = fptr->second;
metadata_type& meta = of.metadataPtr->second;

switch( whence ) {
case SEEK_SET:
newfp = offset;
break;
case SEEK_END:
newfp = meta.recordingSize + offset;
break;
case SEEK_CUR:
newfp = of.filePointer + offset;
break;
default:
errno = EINVAL;
return (off_t)-1;
}
if( newfp<0 || newfp>meta.recordingSize ) {
errno = EINVAL;
return (off_t)-1;
}
// If the new file pointer is equal to the current file pointer,
// we're done very quickly ...
if( newfp==of.filePointer )
return of.filePointer;

// We've got the new file pointer!
// Now skip to the chunk what contains the pointer
filechunks_type& filechunks = chunkCache[of.metadataPtr->first];
filechunks_type::iterator newchunk = filechunks.begin();

while( newchunk!=filechunks.end() && newfp>(newchunk->chunkOffset+newchunk->chunkSize) )
newchunk++;

// Ok, update open file status
of.filePointer = newfp;
of.chunkPtr = newchunk;
return of.filePointer;
}

//////////////////////////////////
//
// int vbs_close(int fd)
//
// close a previously opened
// recording
//
//////////////////////////////////

int vbs_close(int fd) {
openedfiles_type::iterator fptr = openedFiles.find(fd);

if( fptr==openedFiles.end() ) {
errno = EBADF;
return -1;
}
openedFiles.erase( fptr );
return 0;
}


//////////////////////////////////////////
//
// int vbs_setdbg(int newlevel)
//
// Set a new debug level. Higher positive
// numbers yield more detailed output.
//
// Always succeeds and returns the
// previous debug level
//
/////////////////////////////////////////
int vbs_setdbg(int newlevel) {
int rv = dbglev_fn( newlevel );
// prefent function signatures to be printed - always
fnthres_fn( newlevel+1 );
return rv;
}




////////////////////////////////////////
//
// void findMountPoints( void )
//
// scan current rootDir for directories
// named "diskNNNNN"
//
/////////////////////////////////////////

struct isMountpoint {
bool operator()(string const& entry) const {
Regular_Expression rxDisk("^disk[0-9]{1,}$");
struct stat status;
string::size_type slash = entry.find_last_of("/");

// IF there is a slash, we skip it, if there isn't, we
// use the whole string
if( slash==string::npos )
slash = 0;
else
slash += 1;
DEBUG(3, "isMountpoint: checking name " << entry.substr(slash) << endl);
if( !rxDisk.matches(entry.substr(slash)) )
return false;
if( ::lstat(entry.c_str(), &status)<0 ) {
DEBUG(2, "predMountpoint: ::lstat fails on " << entry << " - " << ::strerror(errno) << endl);
return false;
}
// We must have r,x access to the directory [in order to descend into it]
return S_ISDIR(status.st_mode) && (status.st_mode & S_IRUSR) && (status.st_mode & S_IXUSR);
}
};

void findMountPoints( void ) {
DEBUG(2, "findMountPoints[" << rootDir << "]: enter with " << mountpoints.size() << " mountpoints" << endl);
if( mountpoints.size() )
return;
mountpoints = vbs_readdir(rootDir, isMountpoint());
DEBUG(2, "findMountPoints[" << rootDir << "]: exit with " << mountpoints.size() << " mountpoints" << endl);
}


////////////////////////////////////////
//
// void scanRecording(string const&)
//
// scan mountpoints for the requested
// recording
//
/////////////////////////////////////////

void scanRecording(string const& recname) {
// Loop over all mountpoints and check if there are file chunks for this
// recording
for(direntries_type::const_iterator curmp=mountpoints.begin(); curmp!=mountpoints.end(); curmp++)
scanRecordingMountpoint(recname, *curmp);
// Ok, all chunks have been gathered, now do the metadata - if anything
// was found, that is
if( chunkCache.find(recname)==chunkCache.end() )
return;
// Ok, at least *some* chunks have been found!
metadata_type& meta = metadataCache[recname];
filechunks_type& chunks = chunkCache[recname];

// Loop over all chunks - we add up the total size AND we compute, for
// each chunk, the current offset wrt to the beginning of the recording
meta.recordingSize = (off_t)0;
for(filechunks_type::iterator fcptr=chunks.begin(); fcptr!=chunks.end(); fcptr++) {
// Offset is recording size counted so far
fcptr->chunkOffset = meta.recordingSize;
// And add the current chunk to the recording size
meta.recordingSize += fcptr->chunkSize;
DEBUG(3, "scanRecording: " << fcptr->pathToChunk << " (" << fcptr->chunkNumber << ")" << endl);
}
DEBUG(2, "scanRecording: " << recname << " found " << meta.recordingSize << " bytes in " << chunks.size() << " chunks" << endl);
}

void scanRecordingMountpoint(string const& recname, string const& mp) {
struct stat dirstat;
const string dir(mp+"/"+recname);

if( ::lstat(dir.c_str(), &dirstat)<0 ) {
if( errno==ENOENT )
return;
DEBUG(1, "scanRecordingMountpoint(" << recname << ", " << mp << ")/::lstat() fails - " << ::strerror(errno) << endl);
throw errno;
}
// OK, we got the status. If it's not a directory ...
if( !S_ISDIR(dirstat.st_mode) )
throw ENOTDIR;

// Go ahead and scan the directory for chunks
scanRecordingDirectory(recname, dir);
}

struct isRecordingChunk {
isRecordingChunk(string const& recname):
__m_regex( string("^")+recname+"\\.[0-9]{8}$" )
{}

bool operator()(string const& entry) const {
DEBUG(4, "checking entry " << entry << " against " << __m_regex.pattern() << endl);
return __m_regex.matches(entry);
}

Regular_Expression __m_regex;

private:
isRecordingChunk();
};

void scanRecordingDirectory(string const& recname, string const& dir) {
DIR* dirp;
direntries_type chunks;

if( (dirp=::opendir(dir.c_str()))==0 ) {
DEBUG(1, "scanRecordingDirectory(" << recname << ", " << dir << ")/ ::opendir fails - " << ::strerror(errno) << endl);
throw errno;
}
chunks = vbs_readdir(dirp, isRecordingChunk(recname));
::closedir(dirp);

for(direntries_type::const_iterator p=chunks.begin(); p!=chunks.end(); p++)
chunkCache[recname].insert( filechunk_type(dir+"/"+*p) );
//DEBUG(-1, "scanRecordingDirectory(" << recname << ", " << dir << ") chunk " << *p << endl);
}

+ 33
- 0
libvbs.h View File

@@ -0,0 +1,33 @@
#ifndef LIBVBS_H
#define LIBVBS_H

#include <sys/types.h>

/* API for FlexBuff recordings */
#ifdef __cplusplus
extern "C" {
#endif

/* Unfortunately, it IS necessary to initialize the library -
* mostly to verify the root dir where all the data disks are mounted
* does exist and we can read it etc.
* It is possible to re-initialize the library as often as desired,
* as long as no files are open at the time of vbs_init() */
int vbs_init( char const* rootdir );

/* Normal Unix-style file API */
int vbs_open(char const* recname);
ssize_t vbs_read(int fd, void* buf, size_t count);
off_t vbs_lseek(int fd, off_t offset, int whence);
int vbs_close(int fd);


/* Set library debug level. Higher, positive, numbers produce more output. Returns
* previous level, default is "0", no output. */
int vbs_setdbg(int newlevel);

#ifdef __cplusplus
}
#endif

#endif

+ 511
- 14
vbs_fs.cc View File

@@ -2,15 +2,44 @@
#define FUSE_USE_VERSION 26
#include <fuse.h>

// Project includes
#include <auto_array.h>
#include <evlbidebug.h>
#include <regular_expression.h>
#include <dosyscall.h>

// Standardized C++ headers
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <cstddef>
#include <cerrno>
#include <cstring>
#include <csignal>

// Old-style *NIX headers
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>


using namespace std;

DECLARE_EZEXCEPT(vbs_except)
DEFINE_EZEXCEPT(vbs_except)

// Keep a mapping of mountpoint -> monitoring thread
typedef map<string, pthread_t> mpmonitormap_type;

// The mountpoint monitoring threads listen for
// broadcasts on this condition variable - if the
// variable 'stopMonitoring' == true, the threads will stop
volatile bool stopMonitoring = false;
pthread_mutex_t monitorMutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t monitorCondition = PTHREAD_COND_INITIALIZER;


///////////////////////////////////////////////
// Keep state of the FUSE file system in
// a struct of this type. It's a global
@@ -19,28 +48,491 @@ using namespace std;
const string defaultRootDir = "/mnt";

struct vbs_state_type {
string rootDir;
DIR* rootDirPtr;
string rootDir;
mpmonitormap_type mpmonitorMap;

vbs_state_type():
rootDirPtr( 0 ) {}

int openRootDir( void ) {
int rv = 0;

if( rootDir.empty() ) {
DEBUG(-1, "openRootDir/no root dir configured" << endl);
return -EINVAL;
}

vbs_state_type() {}
if( rootDirPtr==0 ) {
if( (rootDirPtr=::opendir(rootDir.c_str()))==0 ) {
rv = errno;
DEBUG(-1, "openRootDir/failed '" << rootDir << "' - " << ::strerror(rv) << endl);
}
}
return rv;
}

~vbs_state_type() {
DEBUG(2, "~vbs_state_type: signal stopMonitoring to threads" << endl);
::pthread_mutex_lock(&monitorMutex);
stopMonitoring = true;
::pthread_cond_broadcast(&monitorCondition);
::pthread_mutex_unlock(&monitorMutex);

DEBUG(2, "~vbs_state_type: waiting for all threads to finish ..." << endl);
for(mpmonitormap_type::iterator p=mpmonitorMap.begin(); p!=mpmonitorMap.end(); p++)
if( int r=::pthread_join(p->second, 0)!=0 )
DEBUG(-1, "vbs_destroy: failed to join thread #" << p->second << " - " << ::strerror(r) << endl);
}
};

vbs_state_type* vbs_state = 0;



///////////////////////////////////////////////
// VBS_FS is going to have to quite often
// grovel over directories, sometimes filtering
// entries and sometimes not.
// This set of function templates allow for flexible
// filtering (or not) of directory entries.
//
// Works on already opened DIR* or std::string (==path)
// The template parameter is the predicate.
// IF the predicate(d_entry) is true, then d_entry
// will be added to the set of entries.
//
///////////////////////////////////////////////
typedef set<string> direntries_type;

// This version will call the predicate with "entry->d_name"
// (i.e. just the name of the entry in the directory) as
// parameter because we do not know the path leading to DIR*
template <typename Predicate>
direntries_type vbs_readdir(DIR* dirp, Predicate pred) {
// Memory for the "dirent" struct may or may not include
// space for the "d_name[]" field. POSIX sais that it's
// almost impossible to pre-allocate the correct amount of memory
// but this is currently one of the better approximations
int eno;
const size_t entryLen = offsetof(struct dirent, d_name)+::pathconf("/", _PC_NAME_MAX) + 1;
struct dirent* entryPtr;
direntries_type rv;
auto_array<unsigned char> dirEntry( new unsigned char[entryLen] );

// Make sure it's rewound
::rewinddir( dirp );

// Check each entry
// ::readdir_r(3) returns 0 on success, entryPtr==NULL if end-of-directory reached
while( (eno=::readdir_r(dirp, (struct dirent*)&dirEntry[0], &entryPtr))==0 ) {
if( entryPtr==0 )
break;
// If predicate returns true, add current entry to result
if( pred(string(entryPtr->d_name)) ) {
EZASSERT2( rv.insert(entryPtr->d_name).second, vbs_except, EZINFO("duplicate insert - " << entryPtr->d_name) );
}
}
// Force succesfull loop ending
EZASSERT2(eno==0, vbs_except, EZINFO("readdir_r failed - " << ::strerror(eno)));

return rv;
}

// This variation, which works on a given path will call the predicate with
// the FULL path to the entry, not just the name from the directory listing!
// So it is "pred( dir + "/" + entry->d_name )"
template <typename Predicate>
direntries_type vbs_readdir(string const& dir, Predicate pred) {
// Memory for the "dirent" struct may or may not include
// space for the "d_name[]" field. POSIX sais that it's
// almost impossible to pre-allocate the correct amount of memory
// but this is currently one of the better approximations
int eno;
DIR* dirp;
const size_t entryLen = offsetof(struct dirent, d_name)+::pathconf("/", _PC_NAME_MAX) + 1;
struct dirent* entryPtr;
direntries_type rv;
auto_array<unsigned char> dirEntry( new unsigned char[entryLen] );

// This is a systemcall so can use ASSERT*() which will
// capture the error message from errno
ASSERT2_NZERO(dirp = ::opendir(dir.c_str()), SCINFO(" opening " << dir));

// Check each entry
// ::readdir_r(3) returns 0 on success, entryPtr==NULL if end-of-directory reached
while( (eno=::readdir_r(dirp, (struct dirent*)&dirEntry[0], &entryPtr))==0 ) {
if( entryPtr==0 )
break;
// If predicate returns true, add current entry to result
if( pred(dir+"/"+entryPtr->d_name) ) {
EZASSERT2( rv.insert(dir+"/"+entryPtr->d_name).second, vbs_except, EZINFO("duplicate insert - " << entryPtr->d_name) );
}
}
// Force succesfull loop ending
EZASSERT2(eno==0, vbs_except, EZINFO("readdir_r failed - " << ::strerror(eno)));
::closedir(dirp);
return rv;
}


// The ones without template just use the NoFilter predicate:
// it returns true for all paths
struct NoFilter {
bool operator()(string const&) const {
return true;
}
};

direntries_type vbs_readdir(DIR* dirp) {
return vbs_readdir(dirp, NoFilter());
}

direntries_type vbs_readdir(string const& dir) {
return vbs_readdir(dir, NoFilter());
}


///////////////////////////////////////////////
//
///////////////////////////////////////////////

template <typename Callback>
struct dirMapper {
typedef map<string, typename Callback::value_type> value_type;

dirMapper():
__m_cb( Callback() )
{}
dirMapper( Callback const& cb ):
__m_cb( cb )
{}

value_type operator()(string const& dir) const {
int eno;
DIR* dirp;
value_type rv;
const size_t entryLen = offsetof(struct dirent, d_name)+::pathconf("/", _PC_NAME_MAX) + 1;
struct dirent* entryPtr;
auto_array<unsigned char> dirEntry( new unsigned char[entryLen] );

// This is a systemcall so can use ASSERT*() which will
// capture the error message from errno
ASSERT2_NZERO(dirp = ::opendir(dir.c_str()), SCINFO(" opening " << dir));

// Check each entry
// ::readdir_r(3) returns 0 on success, entryPtr==NULL if end-of-directory reached
while( (eno=::readdir_r(dirp, (struct dirent*)&dirEntry[0], &entryPtr))==0 ) {
if( entryPtr==0 )
break;
// If predicate returns true, add current entry to result
const string fnm = dir + "/" + entryPtr->d_name;
EZASSERT2(rv.insert( make_pair(fnm, __m_cb(fnm)) ).second,
vbs_except, EZINFO("fail to insert result into map for entry " << entryPtr->d_name));
}
// Force succesfull loop ending
EZASSERT2(eno==0, vbs_except, EZINFO("readdir_r failed - " << ::strerror(eno)));

::closedir(dirp);
return rv;
}
value_type operator()(DIR* dirp) const {
int eno;
value_type rv;
const size_t entryLen = offsetof(struct dirent, d_name)+::pathconf("/", _PC_NAME_MAX) + 1;
struct dirent* entryPtr;
auto_array<unsigned char> dirEntry( new unsigned char[entryLen] );

::rewinddir(dirp);
// Check each entry
// ::readdir_r(3) returns 0 on success, entryPtr==NULL if end-of-directory reached
while( (eno=::readdir_r(dirp, (struct dirent*)&dirEntry[0], &entryPtr))==0 ) {
if( entryPtr==0 )
break;
// If predicate returns true, add current entry to result
const string fnm = entryPtr->d_name;
EZASSERT2(rv.insert( make_pair(fnm, __m_cb(fnm)) ).second,
vbs_except, EZINFO("fail to insert result into map for entry " << entryPtr->d_name));
}
// Force succesfull loop ending
EZASSERT2(eno==0, vbs_except, EZINFO("readdir_r failed - " << ::strerror(eno)));
return rv;
}

Callback __m_cb;
};

#if 0
// Simple fileSize operator - just to test dirMapper construction
struct fileSize {
typedef off_t value_type;

value_type operator()(string const& fnm) const {
int fd;
off_t fs;

if( (fd=::open(fnm.c_str(), O_RDONLY))<0 )
return (off_t)-1;
fs = ::lseek(fd, 0, SEEK_END);
::close(fd);
return fs;
}
};
#endif


// Operator that monitors directories inside
// <rootDir>/diskN/
// to see if they've changed. If they've changed
// then likely there are files added or deleted.
//
// It returns true IF
// * entry is a dir AND
// * entry has r,x such that we can both enter into the dir and read it AND
// * entry is m_time > requested time stamp
struct changedSince {
struct status_type {
bool isDir;
bool hasChanged;

status_type():
isDir( false ), hasChanged( false )
{}

status_type(bool d, bool c):
isDir( d ), hasChanged( c )
{}
};

typedef status_type value_type;

changedSince(time_t ts):
__m_timestamp( ts )
{}

value_type operator()(string const& fnm) const {
// Check if it's a directory and wether it's been changed since the
// last check
struct stat status;

if( ::lstat(fnm.c_str(), &status)<0 )
return status_type();

// We're only interested in directories that have changed since we
// last checked
// We must have r,x access to the directory [in order to descend into it]
return status_type(S_ISDIR(status.st_mode) && (status.st_mode & S_IRUSR) && (status.st_mode & S_IXUSR),
(status.st_mtime>__m_timestamp));
}

private:
// no default c'tor possible!
changedSince();

time_t __m_timestamp;
};


///////////////////////////////////////////////
// Wrapper around ::pthread_create(3) -
// creates a joinable thread with
// ALL signals blocked.
// Takes same arguments as ::pthread_create(3)
// apart from the pthread_attr_t struct
///////////////////////////////////////////////
int vbs_pthread_create(pthread_t* thread, void *(*start_routine)(void*), void *arg) {
int pr, createrv;
sigset_t oldSig, newSig;
pthread_attr_t attr;

// Make sure we have a joinable thread
if( (pr=::pthread_attr_init(&attr))!=0 ) {
DEBUG(-1, "vbs_pthread_create: pthread_attr_init fails - " << ::strerror(pr) << endl);
return pr;
}
if( (pr=::pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE))!=0 ) {
DEBUG(-1, "vbs_pthread_create: pthread_attr_setdetachstate fails - " << ::strerror(pr) << endl);
return pr;
}
// Install a fully filled signal set (i.e. block all of 'm) and save the
// current one
if( sigfillset(&newSig)!=0 ) {
DEBUG(-1, "vbs_pthread_create: sigfillset fails - " << ::strerror(errno) << endl);
return errno;
}
if( (pr=::pthread_sigmask(SIG_SETMASK, &newSig, &oldSig))!=0 ) {
DEBUG(-1, "vbs_pthread_create: pthread_sigmask (setting new mask) fails - " << ::strerror(pr) << endl);
return pr;
}
// Now we're in a determined state and we can safely create the
// thread. We save the return value of this one specifically because
// that will be the eventual return value. We do the cleanup calls
// and if they'll fail we inform the user that but do not return *those*
// error value(s)
createrv = ::pthread_create(thread, &attr, start_routine, arg);
if( createrv!=0 )
DEBUG(-1, "vbs_pthread_create: pthread_create fails - " << ::strerror(createrv) << endl);

// Cleanup phase: put back old signal mask & destroy pthread attributes
if( (pr=::pthread_sigmask(SIG_SETMASK, &oldSig, 0))!=0 )
DEBUG(-1, "vbs_pthread_create: pthread_sigmask (put back old mask) fails - " << ::strerror(pr) << endl);
if( (pr=::pthread_attr_destroy(&attr))!=0 )
DEBUG(-1, "vbs_pthread_create: pthread_attr_destroy fails - " << ::strerror(pr) << endl);

// Phew. Finally done.
return createrv;
}

///////////////////////////////////////////////
// The mountpoint monitor function.
// It is a thread function
///////////////////////////////////////////////

struct isMountpointChanged {
isMountpointChanged(string const& mp, time_t t):
mountPoint( mp ), timeStamp( t )
{}

bool operator()(string const& entry) const {
// Check if it's a directory and wether it's been changed since the
// last check
struct stat status;

if( ::lstat(entry.c_str(), &status)<0 ) {
DEBUG(-1, "mountpointChanged[" << mountPoint << "]::" << entry << " - ::lstat() " << ::strerror(errno) << endl);
return false;
}
// We're only interested in directories that have changed since we
// last checked
// We must have r,x access to the directory [in order to descend into it]
return S_ISDIR(status.st_mode) && (status.st_mode & S_IRUSR) && (status.st_mode & S_IXUSR) && (status.st_mtime>timeStamp);
}

string mountPoint;
time_t timeStamp;
};

void* mountpoint_monitor(void* args) {
typedef dirMapper<changedSince> changeChecker_type;
typedef typename changeChecker_type::value_type checkresult_type;

time_t lastCheck = 0;
const time_t interval = 2;
string const* mountPoint = (string const*)args;

if( mountPoint==0 ) {
DEBUG(-1, "mountpoint_monitor/null-pointer passed as mountpoint!" << endl);
return (void*)0;
}
DEBUG(2, "mountpoint_monitor[" << *mountPoint << "] starting" << endl);

// Fall into our main loop
while( true ) {
bool stop;
struct timeval now;
struct timespec ts;

::gettimeofday(&now, 0);

// Compute timespec for timed wait
ts.tv_sec = lastCheck+interval;
ts.tv_nsec = 0;

::pthread_mutex_lock(&monitorMutex);
if( stopMonitoring==false && now.tv_sec<(lastCheck + interval) )
::pthread_cond_timedwait(&monitorCondition, &monitorMutex, &ts);
stop = stopMonitoring;
::gettimeofday(&now, 0);
::pthread_mutex_unlock(&monitorMutex);

if( stop ) {
DEBUG(4, "mountpoint_monitor[" << *mountPoint << "] detected global stop signal" << endl);
break;
}
DEBUG(4, "[" << *mountPoint << "] check, now=" << now.tv_sec << "+" << now.tv_usec << " scheduled=" << ts.tv_sec << endl);

// 'k - time for a re-check
checkresult_type status = changeChecker_type( changedSince(lastCheck) )( *mountPoint );

// Loop over all
for(checkresult_type::const_iterator ptr=status.begin(); ptr!=status.end(); ptr++)
if( ptr->second.isDir && ptr->second.hasChanged )
DEBUG(-1, "[" << *mountPoint << "] " << ptr->first << endl);
lastCheck = now.tv_sec;
}

DEBUG(2, "mountpoint_monitor[" << *mountPoint << "] stopping" << endl);
delete mountPoint;
return (void*)0;
}

///////////////////////////////////////////////
// Here follow the implementations
// of the file system functions
// that we support
///////////////////////////////////////////////


// Predicate for a directory entry to be a mountpoint
// 1. name must be "disk[0-9]+"
// 2. must refer to a directory
struct isMountpoint {
bool operator()(string const& entry) const {
Regular_Expression rxDisk("^disk[0-9]{1,}$");
struct stat status;
string::size_type slash = entry.find_last_of("/");

// IF there is a slash, we skip it, if there isn't, we
// use the whole string
if( slash==string::npos )
slash = 0;
else
slash += 1;
DEBUG(3, "isMountpoint: checking name " << entry.substr(slash) << endl);
if( !rxDisk.matches(entry.substr(slash)) )
return false;
if( ::lstat(entry.c_str(), &status)<0 ) {
DEBUG(2, "predMountpoint: ::lstat fails on " << entry << " - " << ::strerror(errno) << endl);
return false;
}
// We must have r,x access to the directory [in order to descend into it]
return S_ISDIR(status.st_mode) && (status.st_mode & S_IRUSR) && (status.st_mode & S_IXUSR);
}
};


void* vbs_init(struct fuse_conn_info* /*conn*/) {
DEBUG(-1, "vbs_init" << endl);
DEBUG(1, "vbs_init" << endl);
try {
direntries_type mountPoints = vbs_readdir(vbs_state->rootDir, isMountpoint());
mpmonitormap_type& mpmon( vbs_state->mpmonitorMap );

EZASSERT2( mountPoints.size()>0, vbs_except, EZINFO("No mountpoints found in " << vbs_state->rootDir) );

for( direntries_type::const_iterator mp=mountPoints.begin(); mp!=mountPoints.end(); mp++) {
int r;
pair<mpmonitormap_type::iterator, bool> insres = mpmon.insert( make_pair(*mp, pthread_t()) );

DEBUG(2, "vbs_init: Found mountpoint " << *mp << endl);
EZASSERT2(insres.second==true, vbs_except, EZINFO("Duplicate entry for mountpoint " << *mp));

// Start the monitor thread
if( (r=vbs_pthread_create(&insres.first->second, mountpoint_monitor, new string(*mp)))!=0 )
DEBUG(-1, "vbs_init: Failed to create thread!" << endl);
}
}
catch( const exception& e ) {
DEBUG(-1, "vbs_init/caught deadly exception - " << e.what() << endl);
}
catch( ... ) {
DEBUG(-1, "vbs_init/caught deadly unknown exception" << endl);
}
return (void*)vbs_state;
}

void vbs_destroy(void* ptr) {
DEBUG(-1, "vbs_destroy" << endl);
if( ptr==(void*)vbs_state )
DEBUG(-1, "Yes, it was our global pointer" << endl);
DEBUG(1, "vbs_destroy" << endl);
delete (vbs_state_type*)ptr;
vbs_state = 0;
}
@@ -114,9 +606,9 @@ struct fuse_operations vbs_oper = {
// -f FUSE 'foreground' flag
///////////////////////////////////////////////
struct vbs_settings {
int debug_level;
bool help;
char const* root_dir;
int debug_level;
bool help;
char const* root_dir;

// Initialize with defaults
vbs_settings():
@@ -129,12 +621,10 @@ ostream& operator<<(ostream& os, const vbs_settings& vbo) {
if( vbo.root_dir )
os << "-r " << vbo.root_dir << " ";
if( vbo.help )
os << "-h" << " ";
os << "-h ";
return os;
}

#define KEY_FOREGROUND 42
#define KEY_HELP 666
#define VBSFS_OPT(t, p, v) { t, offsetof(struct vbs_settings, p), v }
struct fuse_opt vbs_options[] = {
VBSFS_OPT("-m %i", debug_level, 0),
@@ -155,7 +645,7 @@ int vbs_option_proc(void* data, const char* arg, int /*key*/, struct fuse_args*
}

void Usage( void ) {
cout << "VBS specific command line arguments:" << endl
cout << "VBS specific command line arguments:" << endl << endl
<< " -m <int> set VBS_FS debug print level to <n>" << endl
<< " higher <n> => more output, default 0 (no output)" << endl
<< " -r <path> path to vbs_fs 'root' directory; assume all disks" << endl
@@ -187,15 +677,22 @@ int main(int argc, char** argv) {

// Do command line parsing
::fuse_opt_parse(&args, (void*)&settings, &vbs_options[0], vbs_option_proc);
dbglev_fn( settings.debug_level );
DEBUG(1, "fuse_opt_arg done [" << settings << "]" << endl);

if( settings.help )
Usage();

// If no root dir given, default to "/mnt"
if( settings.root_dir==0 )
if( settings.help==false && settings.root_dir==0 )
DEBUG(-1, "WARN: No root dir specified for disks, defaulting to " << defaultRootDir << endl);
vbs_state->rootDir = (settings.root_dir==0?defaultRootDir:string(settings.root_dir));

// Before init, do open the rootdir - (no access(2) or stat(2) because
// that means that between access/stat and the open the dir may have
// gone, its permissions altered or god knows what
if( const int r = vbs_state->openRootDir() )
return r;

// Now it's about time to drop into FUSE's main loop
DEBUG(3, "dropping into fuse_main" << endl);


Loading…
Cancel
Save