Difference between revisions of "QMfs"
Jump to navigation
Jump to search
(More info about the current status) |
m (Changed sectioning) |
||
| Line 3: | Line 3: | ||
Current status: '''proof-of-concept''' | Current status: '''proof-of-concept''' | ||
| + | ==Development Plan== | ||
===Limitations=== | ===Limitations=== | ||
* The account and filename are hardcoded | * The account and filename are hardcoded | ||
Revision as of 11:42, 7 May 2009
A Fuse-based filesystem that uses QMClient to access dynamic files.
Current status: proof-of-concept
Contents
Development Plan
Limitations
- The account and filename are hardcoded
- The filesystem is read-only
- It does not deal with multifiles
- It probably leaks memory (any good resources for how to check?)
- It will fail badly if things go wrong (e.g. qm isn't started)
Future Improvements
- Deal with the problems above
- Set it up to mount over the top of the system directory
- Change it to allow a persistent QM connection ?
- Remove dependencies on Fuse (you can package it up, I think)
- Add a preference for mark mapping
- Export item names with special characters changed to their QM equivalents (e.g. '=' ⟼ '%E')
Installation instructions
- Install Fuse, along with the development files. In Ubuntu, that's fuse and fuse-dev.
- Make sure there's a copy of libqmcli.so somewhere in your library paths (I did
ln -s /usr/qmsys/bin/libqmcli.so /usr/lib/
, but it might already be there depending on your installation). - Copy to code below to a file called qmfs.c (it doesn't matter where).
- Compile:
gcc -Wall `pkg-config fuse --cflags --libs` -lqmcli -I/usr/qmsys/gplsrc qmfs.c -o qmfs
- Create an account called TESTACC in the usual way.
- Create a (dynamic) file called TESTFILE in that account.
- Put some test data in it.
- Create a directory to mount to (e.g. 'qmfstest').
- Make sure qm is running.
- Start the demo with
./qmfs qmfstest
(where qmfstest is the mountpoint) - Test it! Try
ls qmfstest
andcat qmfstest/SOMEITEM
- To unmount the filesystem, use
fusermount -u qmfstest
File
This is C code -- qmfs.c
// QMFS -- an OpenQM filesystem wrapper, built on Fuse and QMConnect
#define FUSE_USE_VERSION 26
#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <qmclient.h>
// Some consts -- should stop being consts later. :o)
static const char* account = "TESTACC";
static const char* fileName = "TESTFILE";
#define LIST_NO 1
#define QM_MARK (char) 254
#define debugging 1
/* Returns true if the item exists, false if not.
* Requires there to be an open QMConnection first.
*/
static int itemExists(const int fileNo, const char* itemId) {
#ifdef debugging
printf("itemExists(%d,\"%s\")\n", fileNo, itemId);
#endif
int ret;
// Get the list of items
QMSelect(fileNo, LIST_NO);
char* readList = QMReadList(LIST_NO);
char* curPos = readList;
while(1) {
curPos = strstr(curPos, itemId);
if (curPos == NULL) {
// the string was not found
ret = 0;
break;
}
// This is safe, since we know it contains the whole string
curPos += strlen(itemId);
if (strlen(curPos) == 0) {
// we are at the end of the list, and the item matched.
ret = 1;
break;
}
if (curPos[0] == QM_MARK) {
// the next char is a delimeter, so the string was found
ret = 1;
break;
}
// otherwise the itemid matched, but only the first part (e.g. 'bob' matched 'bobo').
// try again further up the list
}
QMFree(readList);
return ret;
}
/* Gets the permissions ('attributes') of a path.
*/
static int qm_getattr(const char *path, struct stat *stbuf) {
#ifdef debugging
printf("qm_getattr(\"%s\",stbuf)\n", path);
#endif
// An int representation of the perms.
int res = 0;
// Reset the stats
memset(stbuf, 0, sizeof(struct stat));
// If the path is just the root:
if (strcmp(path, "/") == 0) {
// Permissions, and set as directory
stbuf->st_mode = S_IFDIR | 0775;
// Number of hardlinks
// Not quite sure how this works, but I think it's -- '/' and '/.'
stbuf->st_nlink = 2;
} else {
// Get a connection
int connect = QMConnectLocal(account);
// QMConnectLocal returns 1 if connected and 0 if not
// if not, can use QMError to retrive error message
if (connect == 0) {
// printf("Could not get a connection.\n");
return -EIO;
}
int fileNo = QMOpen(fileName);
if (fileNo == 0) {
// couldn't open the file
// printf("Couldn't open the file.\n");
// TODO: this is bad; it leaks!
return -EIO;
}
char* itemId = path+1;
// IMPORTANT: might this cause an overflow if 'path' is too small?
if (itemExists(fileNo, itemId)) {
// read the item to get the size
int errNo;
char* item = QMRead(fileNo, itemId, &errNo);
// errNo contains SV_OK if it's okay, SV_ELSE if it can't be found (and
// returns an empty string) or SV_ON_ERROR if there's a real problem
if (errno == SV_ON_ERROR) {
// Do something drastic...
//TODO decide what goes here!
}
// A regular file, and permissions
stbuf->st_mode = S_IFREG | 0444;
// Number of hardlinks (just one)
stbuf->st_nlink = 1;
// The filesize, in bytes
stbuf->st_size = strlen(item);
// deallocate the item
QMFree(item);
} else {
// An error value, to say (a component of) the path does not exist
// (or the path is empty)
res = -ENOENT;
}
// Close the connection
QMDisconnect();
}
return res;
}
/* List a directory's contents.
*/
static int qm_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi) {
// path is the path, buf is where we put the file info
// filler is a function which 'fills' the buffer
// I don't know what offset or fi do.
#ifdef debugging
printf("qm_readdir(\"%s\", buf, filler, offset, fi)\n", path);
//printf("qm_readdir(\"%s\", buf, filler, %d, fi)\n", path, offset);
#endif
(void) offset;
(void) fi;
// Check if a real directory is begin asked for
if (strcmp(path, "/") != 0) {
// No such path
return -ENOENT;
}
// Add the two standard dirs
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
// Get a connection
int connect = QMConnectLocal(account);
// QMConnectLocal returns 1 if connected and 0 if not
// if not, can use QMError to retrive error message
if (connect == 0) {
// printf("Could not get a connection.\n");
return -EIO;
}
// Get the list of items
int fileNo = QMOpen(fileName);
if (fileNo == 0) {
// couldn't open the file
// printf("Couldn't open the file.\n");
// TODO: this is bad; it leaks!
return -EIO;
}
QMSelect(fileNo, LIST_NO);
char* readList = QMReadList(LIST_NO);
char* item;
for ( item = strtok(readList, "\xFE");
item != NULL;
item = strtok(NULL, "\xFE") ) {
// Add them to the list
filler(buf, item, NULL, 0);
}
// Close the connection
QMFree(readList);
QMDisconnect();
// No errors
return 0;
}
/* Open files for reading / writing.
*/
static int qm_open(const char *path, struct fuse_file_info *fi) {
#ifdef debugging
printf("qm_open(\"%s\", fi)\n", path);
#endif
// path -- path to open
// fi -- how the file should be opened
// Return value -- 0 for success
int ret = 0;
// [Check path exists]
// If the path is empty or is the directory
if ((strlen(path) == 0) || strcmp(path, "/") == 0) {
// File does not exist
return -ENOENT;
}
// Get a connection
int connect = QMConnectLocal(account);
// QMConnectLocal returns 1 if connected and 0 if not
// if not, can use QMError to retrive error message
if (connect == 0) {
// printf("Could not get a connection.\n");
return -EIO;
}
int fileNo = QMOpen(fileName);
if (fileNo == 0) {
// couldn't open the file
// printf("Couldn't open the file.\n");
// TODO: this is bad; it leaks!
return -EIO;
}
char* itemId = path+1;
if (itemExists(fileNo, itemId)) {
// if the user wants to do anything other than reading
if ((fi->flags & 3) != O_RDONLY) {
// permission denied
ret = -EACCES;
}
} else {
// set an error
// file does not exist
ret = -ENOENT;
}
// Close the connection
QMDisconnect();
return ret;
}
/* Read the contents of a file
*/
static int qm_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi) {
#ifdef debugging
printf("qm_read(\"%s\", buf, size, offset, fi)\n", path);
//printf("qm_read(\"%s\", buf, %d, %d, fi)\n", path, size, offset);
#endif
// path -- path to the file
// buf -- buffer into which to copy the info
// size -- size of buf / how much to read
// offset -- read offset
// fi -- more file info (e.g. open for read only, write, etc.)
// ignore fi
(void) fi;
// number of bytes read
size_t len;
// [Check path exists]
// If the path is empty or is the directory
if ((strlen(path) == 0) || strcmp(path, "/") == 0) {
// File does not exist
return -ENOENT;
}
// Get a connection
int connect = QMConnectLocal(account);
// QMConnectLocal returns 1 if connected and 0 if not
// if not, can use QMError to retrive error message
if (connect == 0) {
// printf("Could not get a connection.\n");
return -EIO;
}
// Read the item
int fileNo = QMOpen(fileName);
if (fileNo == 0) {
// couldn't open the file
// printf("Couldn't open the file.\n");
// TODO: this is bad; it leaks!
return -EIO;
}
char* itemId = path+1;
int errNo;
char* item = QMRead(fileNo, itemId, &errNo);
// errNo contains SV_OK if it's okay, SV_ELSE if it can't be found (and
// returns an empty string) or SV_ON_ERROR if there's a real problem
if (errNo == SV_ON_ERROR) {
// do something drastic!
//TODO decide what goes here!
}
// TODO: is this right?
// If the item does not exist:
if (errNo == SV_ELSE) {
// file does not exist
return -ENOENT;
}
// Divide the data into chunks
len = strlen(item);
if (offset < len) {
if (offset + size > len) {
size = len - offset;
}
memcpy(buf, item + offset, size);
} else {
size = 0;
}
// Close connection
QMFree(item);
QMDisconnect();
// return how much was actually read
return size;
}
/* Structure containing the fuse operations implemented
*/
static struct fuse_operations qm_oper = {
.getattr = qm_getattr,
.readdir = qm_readdir,
.open = qm_open,
.read = qm_read,
};
int main(int argc, char *argv[]) {
return fuse_main(argc, argv, &qm_oper, NULL);
}