#include "DirHandle.h" #include "Workspace.h" #include <algorithm> #include <dirent.h> #include <fcntl.h> #include <fuse.h> #include <iostream> #include <string> #include <sys/attr.h> #include <sys/stat.h> #include <sys/xattr.h> #include <tclap/CmdLine.h> #include <unistd.h> #include <vector> using std::cout; using std::cerr; using std::endl; using std::string; using std::transform; using std::vector; Workspace ws; #define G_PREFIX "org" #define G_KAUTH_FILESEC_XATTR G_PREFIX ".apple.system.Security" #define A_PREFIX "com" #define A_KAUTH_FILESEC_XATTR A_PREFIX ".apple.system.Security" #define XATTR_APPLE_PREFIX "com.apple." //--------------------------------------------------------------------------- // Filesystem operations //--------------------------------------------------------------------------- static int fsclient_getattr(const char *path, struct stat *stbuf) { if (strcmp(path, "/.status") == 0) { stbuf->st_mode = S_IFREG | 0444; stbuf->st_nlink = 1; stbuf->st_size = ws.status().size(); return 0; } int status = lstat(ws.path(path).c_str(), stbuf); if (status == 0) { return 0; } else { return -errno; } } static int fsclient_open(const char *path, struct fuse_file_info *fi) { if (strcmp(path, "/.status") == 0) { return 0; } int fd = open(ws.path(path).c_str(), fi->flags); if (fd >= 0) { fi->fh = fd; return 0; } else { return -errno; } } static int fsclient_flush(const char *path, struct fuse_file_info *fi) { int res; (void)path; res = close(dup(fi->fh)); if (res == -1) { return -errno; } return 0; } static int fsclient_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { if (strcmp(path, "/.status") == 0) { string status = ws.status(); if (offset > status.size()) return 0; if (offset + size > status.size()) size = status.size() - offset; memcpy(buf, status.c_str() + offset, size); return size; } ssize_t num_read = pread(fi->fh, buf, size, offset); if (num_read == -1) { return -errno; } return num_read; } static int fsclient_release(const char *path, struct fuse_file_info *fi) { if (strcmp(path, "/.status") == 0) { return 0; } int status = close(fi->fh); if (status == 0) { return 0; } else { return -errno; } } static int fsclient_rename(const char *from, const char *to) { int status = rename(ws.path(from).c_str(), ws.path(to).c_str()); if (status == -1) return -errno; ws.enqueue(Notification(Notification::RENAME, from, to)); return 0; } static int fsclient_mknod(const char *path, mode_t mode, dev_t rdev) { int res; if (S_ISFIFO(mode)) { res = mkfifo(ws.path(path).c_str(), mode); } else { res = mknod(ws.path(path).c_str(), mode, rdev); } if (res == -1) { return -errno; } ws.enqueue(Notification(Notification::MKNOD, path)); return 0; } // Right now, we assign the DIR* to the fi->fh field. static int fsclient_opendir(const char *path, struct fuse_file_info *fi) { DIR *dir = opendir(ws.path(path).c_str()); if (dir == NULL) { return -errno; } else { // Create "DirHandle" instance. DirHandle *handle = new DirHandle(dir); fi->fh = reinterpret_cast<unsigned long>(handle); return 0; } } static int fsclient_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { (void) path; // Will not be used. // TODO this needs special handling to inject special files into '/' DirHandle *dirHandle = reinterpret_cast<DirHandle *>(fi->fh); if (offset != dirHandle->offset) { seekdir(dirHandle->dir, offset); dirHandle->entry = NULL; dirHandle->offset = offset; } while (1) { struct stat st; off_t nextoff; if (!dirHandle->entry) { // Note: I'm not sure how to detect if this failed or is just at // the end. It returns NULL in both cases. dirHandle->entry = readdir(dirHandle->dir); if (!dirHandle->entry) break; } memset(&st, 0, sizeof(st)); st.st_ino = dirHandle->entry->d_ino; st.st_mode = DTTOIF(dirHandle->entry->d_type); nextoff = telldir(dirHandle->dir); if (filler(buf, dirHandle->entry->d_name, &st, nextoff)) break; dirHandle->entry = NULL; dirHandle->offset = nextoff; } if (strcmp(path, "/") == 0) { filler(buf, ".status", NULL, 0); } return 0; } static int fsclient_releasedir(const char *path, struct fuse_file_info *fi) { (void) path; // Will not be used. DirHandle *dirHandle = reinterpret_cast<DirHandle *>(fi->fh); closedir(dirHandle->dir); delete dirHandle; return 0; } static int fsclient_write(const char *path, const char *buf, size_t nbyte, off_t offset, struct fuse_file_info *fi) { ssize_t num_written = pwrite(fi->fh, buf, nbyte, offset); if (num_written == -1) { return -errno; } ws.enqueue(Notification(Notification::WRITE, path)); return num_written; } static int fsclient_create(const char *path, mode_t mode, struct fuse_file_info *fi) { int fd = open(ws.path(path).c_str(), fi->flags, mode); if (fd == -1) { return -errno; } ws.enqueue(Notification(Notification::CREATE, path)); fi->fh = fd; return 0; } static int fsclient_mkdir(const char *path, mode_t mode) { int err = mkdir(ws.path(path).c_str(), mode); if (err == -1) { return -errno; } return 0; } static int fsclient_unlink(const char *path) { int status = unlink(ws.path(path).c_str()); if (status == -1) return -errno; return 0; } // Note: This is pretty much the same as the fsclient example implementation. // We need to do the same thing, and, there are a lot of macros in use that // are not really documented anywhere. static int fsclient_setattr_x(const char *path, struct setattr_x *attr) { int res; uid_t uid = -1; gid_t gid = -1; if (SETATTR_WANTS_MODE(attr)) { res = lchmod(path, attr->mode); if (res == -1) { return -errno; } } if (SETATTR_WANTS_UID(attr)) { uid = attr->uid; } if (SETATTR_WANTS_GID(attr)) { gid = attr->gid; } if ((uid != -1) || (gid != -1)) { res = lchown(path, uid, gid); if (res == -1) { return -errno; } } if (SETATTR_WANTS_SIZE(attr)) { res = truncate(path, attr->size); if (res == -1) { return -errno; } } if (SETATTR_WANTS_MODTIME(attr)) { struct timeval tv[2]; if (!SETATTR_WANTS_ACCTIME(attr)) { gettimeofday(&tv[0], NULL); } else { tv[0].tv_sec = attr->acctime.tv_sec; tv[0].tv_usec = attr->acctime.tv_nsec / 1000; } tv[1].tv_sec = attr->modtime.tv_sec; tv[1].tv_usec = attr->modtime.tv_nsec / 1000; res = utimes(path, tv); if (res == -1) { return -errno; } } if (SETATTR_WANTS_CRTIME(attr)) { struct attrlist attributes; attributes.bitmapcount = ATTR_BIT_MAP_COUNT; attributes.reserved = 0; attributes.commonattr = ATTR_CMN_CRTIME; attributes.dirattr = 0; attributes.fileattr = 0; attributes.forkattr = 0; attributes.volattr = 0; res = setattrlist(path, &attributes, &attr->crtime, sizeof(struct timespec), FSOPT_NOFOLLOW); if (res == -1) { return -errno; } } if (SETATTR_WANTS_CHGTIME(attr)) { struct attrlist attributes; attributes.bitmapcount = ATTR_BIT_MAP_COUNT; attributes.reserved = 0; attributes.commonattr = ATTR_CMN_CHGTIME; attributes.dirattr = 0; attributes.fileattr = 0; attributes.forkattr = 0; attributes.volattr = 0; res = setattrlist(path, &attributes, &attr->chgtime, sizeof(struct timespec), FSOPT_NOFOLLOW); if (res == -1) { return -errno; } } if (SETATTR_WANTS_BKUPTIME(attr)) { struct attrlist attributes; attributes.bitmapcount = ATTR_BIT_MAP_COUNT; attributes.reserved = 0; attributes.commonattr = ATTR_CMN_BKUPTIME; attributes.dirattr = 0; attributes.fileattr = 0; attributes.forkattr = 0; attributes.volattr = 0; res = setattrlist(path, &attributes, &attr->bkuptime, sizeof(struct timespec), FSOPT_NOFOLLOW); if (res == -1) { return -errno; } } if (SETATTR_WANTS_FLAGS(attr)) { res = lchflags(path, attr->flags); if (res == -1) { return -errno; } } return 0; } // Note: This is pretty much the same as the fsclient example implementation. // We need to do the same thing, and, there are a lot of macros in use that // are not really documented anywhere. static int fsclient_fsetattr_x(const char *path, struct setattr_x *attr, struct fuse_file_info *fi) { int res; uid_t uid = -1; gid_t gid = -1; if (SETATTR_WANTS_MODE(attr)) { res = fchmod(fi->fh, attr->mode); if (res == -1) { return -errno; } } if (SETATTR_WANTS_UID(attr)) { uid = attr->uid; } if (SETATTR_WANTS_GID(attr)) { gid = attr->gid; } if ((uid != -1) || (gid != -1)) { res = fchown(fi->fh, uid, gid); if (res == -1) { return -errno; } } if (SETATTR_WANTS_SIZE(attr)) { res = ftruncate(fi->fh, attr->size); if (res == -1) { return -errno; } } if (SETATTR_WANTS_MODTIME(attr)) { struct timeval tv[2]; if (!SETATTR_WANTS_ACCTIME(attr)) { gettimeofday(&tv[0], NULL); } else { tv[0].tv_sec = attr->acctime.tv_sec; tv[0].tv_usec = attr->acctime.tv_nsec / 1000; } tv[1].tv_sec = attr->modtime.tv_sec; tv[1].tv_usec = attr->modtime.tv_nsec / 1000; res = futimes(fi->fh, tv); if (res == -1) { return -errno; } } if (SETATTR_WANTS_CRTIME(attr)) { struct attrlist attributes; attributes.bitmapcount = ATTR_BIT_MAP_COUNT; attributes.reserved = 0; attributes.commonattr = ATTR_CMN_CRTIME; attributes.dirattr = 0; attributes.fileattr = 0; attributes.forkattr = 0; attributes.volattr = 0; res = fsetattrlist(fi->fh, &attributes, &attr->crtime, sizeof(struct timespec), FSOPT_NOFOLLOW); if (res == -1) { return -errno; } } if (SETATTR_WANTS_CHGTIME(attr)) { struct attrlist attributes; attributes.bitmapcount = ATTR_BIT_MAP_COUNT; attributes.reserved = 0; attributes.commonattr = ATTR_CMN_CHGTIME; attributes.dirattr = 0; attributes.fileattr = 0; attributes.forkattr = 0; attributes.volattr = 0; res = fsetattrlist(fi->fh, &attributes, &attr->chgtime, sizeof(struct timespec), FSOPT_NOFOLLOW); if (res == -1) { return -errno; } } if (SETATTR_WANTS_BKUPTIME(attr)) { struct attrlist attributes; attributes.bitmapcount = ATTR_BIT_MAP_COUNT; attributes.reserved = 0; attributes.commonattr = ATTR_CMN_BKUPTIME; attributes.dirattr = 0; attributes.fileattr = 0; attributes.forkattr = 0; attributes.volattr = 0; res = fsetattrlist(fi->fh, &attributes, &attr->bkuptime, sizeof(struct timespec), FSOPT_NOFOLLOW); if (res == -1) { return -errno; } } if (SETATTR_WANTS_FLAGS(attr)) { res = fchflags(fi->fh, attr->flags); if (res == -1) { return -errno; } } return 0; } static int fsclient_statfs(const char *path, struct statvfs *stbuf) { int res; res = statvfs(ws.path(path).c_str(), stbuf); if (res == -1) { return -errno; } return 0; } static int fsclient_fsync(const char *path, int isdatasync, struct fuse_file_info *fi) { int res; (void) path; (void) isdatasync; res = fsync(fi->fh); if (res == -1) { return -errno; } return 0; } static int fsclient_setxattr(const char *path, const char *name, const char *value, size_t size, int flags, uint32_t position) { int res; if (!strncmp(name, XATTR_APPLE_PREFIX, sizeof(XATTR_APPLE_PREFIX) - 1)) { flags &= ~(XATTR_NOSECURITY); } if (!strcmp(name, A_KAUTH_FILESEC_XATTR)) { char new_name[MAXPATHLEN]; memcpy(new_name, A_KAUTH_FILESEC_XATTR, sizeof(A_KAUTH_FILESEC_XATTR)); memcpy(new_name, G_PREFIX, sizeof(G_PREFIX) - 1); res = setxattr(ws.path(path).c_str(), new_name, value, size, position, XATTR_NOFOLLOW); } else { res = setxattr(ws.path(path).c_str(), name, value, size, position, XATTR_NOFOLLOW); } if (res == -1) { return -errno; } return 0; } static int fsclient_getxattr(const char *path, const char *name, char *value, size_t size, uint32_t position) { int res; if (strcmp(name, A_KAUTH_FILESEC_XATTR) == 0) { char new_name[MAXPATHLEN]; memcpy(new_name, A_KAUTH_FILESEC_XATTR, sizeof(A_KAUTH_FILESEC_XATTR)); memcpy(new_name, G_PREFIX, sizeof(G_PREFIX) - 1); res = getxattr(ws.path(path).c_str(), new_name, value, size, position, XATTR_NOFOLLOW); } else { res = getxattr(ws.path(path).c_str(), name, value, size, position, XATTR_NOFOLLOW); } if (res == -1) { return -errno; } return res; } static int fsclient_listxattr(const char *path, char *list, size_t size) { ssize_t res = listxattr(ws.path(path).c_str(), list, size, XATTR_NOFOLLOW); if (res > 0) { if (list) { size_t len = 0; char *curr = list; do { size_t thislen = strlen(curr) + 1; if (strcmp(curr, G_KAUTH_FILESEC_XATTR) == 0) { memmove(curr, curr + thislen, res - len - thislen); res -= thislen; break; } curr += thislen; len += thislen; } while (len < res); } else { /* ssize_t res2 = getxattr(path, G_KAUTH_FILESEC_XATTR, NULL, 0, 0, XATTR_NOFOLLOW); if (res2 >= 0) { res -= sizeof(G_KAUTH_FILESEC_XATTR); } */ } } if (res == -1) { return -errno; } return res; } static int fsclient_removexattr(const char *path, const char *name) { int res; if (strcmp(name, A_KAUTH_FILESEC_XATTR) == 0) { char new_name[MAXPATHLEN]; memcpy(new_name, A_KAUTH_FILESEC_XATTR, sizeof(A_KAUTH_FILESEC_XATTR)); memcpy(new_name, G_PREFIX, sizeof(G_PREFIX) - 1); res = removexattr(path, new_name, XATTR_NOFOLLOW); } else { res = removexattr(path, name, XATTR_NOFOLLOW); } if (res == -1) { return -errno; } return 0; } //--------------------------------------------------------------------------- // Entry point //--------------------------------------------------------------------------- static struct fuse_operations fsclient_operations = { .create = fsclient_create, .flush = fsclient_flush, .fsetattr_x = fsclient_fsetattr_x, .fsync = fsclient_fsync, .getattr = fsclient_getattr, .getxattr = fsclient_getxattr, .listxattr = fsclient_listxattr, .mkdir = fsclient_mkdir, .mknod = fsclient_mknod, .open = fsclient_open, .opendir = fsclient_opendir, .read = fsclient_read, .readdir = fsclient_readdir, .rename = fsclient_rename, .release = fsclient_release, .releasedir = fsclient_releasedir, .removexattr = fsclient_removexattr, .setattr_x = fsclient_setattr_x, .setxattr = fsclient_setxattr, .statfs = fsclient_statfs, .write = fsclient_write, .unlink = fsclient_unlink, }; int main(int argc, char **argv) { string workspace; string mount; bool debug = false; try { TCLAP::CmdLine cmd("./fsclient -w WS_DIR -m MOUNT_DIR", ' ', "0.1.0", true); TCLAP::ValueArg<string> workspaceArg("w", "workspace", "Local p4 workspace directory", true, "", "string", cmd); TCLAP::ValueArg<string> mountArg("m", "mount", "Local directory to mount as filesystem", true, "", "string", cmd); TCLAP::MultiSwitchArg debugArg("d", "debug", "Run in foreground and print debug messages", cmd); cmd.parse(argc, const_cast<const char * const*>(argv)); workspace = workspaceArg.getValue(); mount = mountArg.getValue(); debug = debugArg.getValue(); } catch (TCLAP::ArgException & argException) { cerr << argException.error() << " for " << argException.argId() << endl; } cout << "Mirroring workspace: " << workspace << endl; cout << "Mounting at: " << mount << endl; ws.setWorkspace(workspace); int status = setenv("P4CONFIG", (workspace + "/.p4config").c_str(), 1); if (status == -1) { int err = errno; cerr << strerror(err) << endl; return err; } const char *debugArgv[] = {argv[0], "-d", mount.c_str()}; const char *prodArgv[] = {argv[0], mount.c_str()}; const char **fuseArgv; int fuseArgc; if (debug) { fuseArgc = 3; fuseArgv = debugArgv; } else { fuseArgc = 2; fuseArgv = prodArgv; } return fuse_main(fuseArgc, const_cast<char **>(fuseArgv), &fsclient_operations, NULL); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#3 | 16236 | tjuricek |
Revise FUSE-client to call p4 reconcile intelligently. This uses the main FUSE callbacks like a loopback with a notification mechanism. After no real disk access after a short period of time (like 500ms) we'll trigger a call to p4 reconcile. The "interface" to this application is currently just a file handle: /.status - Lists "ok" if there's no errors, otherwise, outputs a list of messages |
||
#2 | 16208 | tjuricek |
Naive implementation that adds guesses at rename and unlink abilities. It's becoming *very* apparent that we need a different approach to handling file system events. We likely need to create a log of "here's what I've done" and then after a certain period of time trigger a system that allows you to possibly just reconcile the changes. Matching filesystem calls to p4 commands ends up with *a lot* of p4 commands. |
||
#1 | 16129 | tjuricek |
Rename/move files again... this time to the hyphenated-approach. |
||
//guest/tjuricek/file_system_client/main/main.cpp | |||||
#1 | 16119 | tjuricek | Rename/move to meet workshop project conventions. | ||
//guest/tjuricek/fsclient/main.cpp | |||||
#1 | 16118 | tjuricek |
FSClient initial version: handles add, edit This is a proof-of-concept app that mirrors an existing Perforce workspace to handle running commands like "p4 add" and "p4 edit" automatically when your apps add and write files. See the readme for more information. |