QMfs
Jump to navigation
Jump to search
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). Warning for some reason, I'm not sure why, it breaks unless you run in debug mode. Add a '-d' to the line above to enable it. - 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); }