QMfs

From ScarletDME
Jump to navigation Jump to search

A Fuse-based filesystem that uses QMClient to access dynamic files.

Current status: proof-of-concept

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

  1. Install Fuse, along with the development files. In Ubuntu, that's fuse and fuse-dev.
  2. 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).
  3. Copy to code below to a file called qmfs.c (it doesn't matter where).
  4. Compile:
    gcc -Wall `pkg-config fuse --cflags --libs` -lqmcli -I/usr/qmsys/gplsrc qmfs.c -o qmfs
  5. Create an account called TESTACC in the usual way.
  6. Create a (dynamic) file called TESTFILE in that account.
  7. Put some test data in it.
  8. Create a directory to mount to (e.g. 'qmfstest').
  9. Make sure qm is running.
  10. 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.
  11. Test it! Try
    ls qmfstest
    and
    cat qmfstest/SOMEITEM
  12. 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);
}