From 71fe2852f47183638488a233cb1ef390fc48b2ea Mon Sep 17 00:00:00 2001 From: David van Moolenbroek Date: Mon, 25 Jan 2010 23:18:02 +0000 Subject: [PATCH] HGFS - VMware Shared Folders file system server --- etc/system.conf | 16 ++ man/man8/hgfs.8 | 76 ++++++ servers/Makefile | 1 + servers/hgfs/Makefile | 34 +++ servers/hgfs/const.h | 13 ++ servers/hgfs/dentry.c | 194 +++++++++++++++ servers/hgfs/glo.h | 12 + servers/hgfs/handle.c | 78 +++++++ servers/hgfs/inc.h | 37 +++ servers/hgfs/inode.c | 299 ++++++++++++++++++++++++ servers/hgfs/inode.h | 93 ++++++++ servers/hgfs/libhgfs/Makefile | 26 +++ servers/hgfs/libhgfs/attr.c | 86 +++++++ servers/hgfs/libhgfs/backdoor.S | 106 +++++++++ servers/hgfs/libhgfs/channel.c | 146 ++++++++++++ servers/hgfs/libhgfs/const.h | 62 +++++ servers/hgfs/libhgfs/dir.c | 71 ++++++ servers/hgfs/libhgfs/error.c | 40 ++++ servers/hgfs/libhgfs/file.c | 182 +++++++++++++++ servers/hgfs/libhgfs/glo.h | 6 + servers/hgfs/libhgfs/hgfs.h | 63 +++++ servers/hgfs/libhgfs/inc.h | 19 ++ servers/hgfs/libhgfs/link.c | 73 ++++++ servers/hgfs/libhgfs/misc.c | 73 ++++++ servers/hgfs/libhgfs/path.c | 73 ++++++ servers/hgfs/libhgfs/proto.h | 51 ++++ servers/hgfs/libhgfs/rpc.c | 94 ++++++++ servers/hgfs/libhgfs/time.c | 69 ++++++ servers/hgfs/libhgfs/type.h | 7 + servers/hgfs/link.c | 403 ++++++++++++++++++++++++++++++++ servers/hgfs/lookup.c | 333 ++++++++++++++++++++++++++ servers/hgfs/main.c | 232 ++++++++++++++++++ servers/hgfs/misc.c | 27 +++ servers/hgfs/mount.c | 94 ++++++++ servers/hgfs/name.c | 56 +++++ servers/hgfs/optset.c | 128 ++++++++++ servers/hgfs/optset.h | 30 +++ servers/hgfs/path.c | 112 +++++++++ servers/hgfs/proto.h | 82 +++++++ servers/hgfs/read.c | 239 +++++++++++++++++++ servers/hgfs/stat.c | 154 ++++++++++++ servers/hgfs/table.c | 46 ++++ servers/hgfs/type.h | 19 ++ servers/hgfs/util.c | 63 +++++ servers/hgfs/verify.c | 118 ++++++++++ servers/hgfs/write.c | 164 +++++++++++++ 46 files changed, 4400 insertions(+) create mode 100644 man/man8/hgfs.8 create mode 100644 servers/hgfs/Makefile create mode 100644 servers/hgfs/const.h create mode 100644 servers/hgfs/dentry.c create mode 100644 servers/hgfs/glo.h create mode 100644 servers/hgfs/handle.c create mode 100644 servers/hgfs/inc.h create mode 100644 servers/hgfs/inode.c create mode 100644 servers/hgfs/inode.h create mode 100644 servers/hgfs/libhgfs/Makefile create mode 100644 servers/hgfs/libhgfs/attr.c create mode 100644 servers/hgfs/libhgfs/backdoor.S create mode 100644 servers/hgfs/libhgfs/channel.c create mode 100644 servers/hgfs/libhgfs/const.h create mode 100644 servers/hgfs/libhgfs/dir.c create mode 100644 servers/hgfs/libhgfs/error.c create mode 100644 servers/hgfs/libhgfs/file.c create mode 100644 servers/hgfs/libhgfs/glo.h create mode 100644 servers/hgfs/libhgfs/hgfs.h create mode 100644 servers/hgfs/libhgfs/inc.h create mode 100644 servers/hgfs/libhgfs/link.c create mode 100644 servers/hgfs/libhgfs/misc.c create mode 100644 servers/hgfs/libhgfs/path.c create mode 100644 servers/hgfs/libhgfs/proto.h create mode 100644 servers/hgfs/libhgfs/rpc.c create mode 100644 servers/hgfs/libhgfs/time.c create mode 100644 servers/hgfs/libhgfs/type.h create mode 100644 servers/hgfs/link.c create mode 100644 servers/hgfs/lookup.c create mode 100644 servers/hgfs/main.c create mode 100644 servers/hgfs/misc.c create mode 100644 servers/hgfs/mount.c create mode 100644 servers/hgfs/name.c create mode 100644 servers/hgfs/optset.c create mode 100644 servers/hgfs/optset.h create mode 100644 servers/hgfs/path.c create mode 100644 servers/hgfs/proto.h create mode 100644 servers/hgfs/read.c create mode 100644 servers/hgfs/stat.c create mode 100644 servers/hgfs/table.c create mode 100644 servers/hgfs/type.h create mode 100644 servers/hgfs/util.c create mode 100644 servers/hgfs/verify.c create mode 100644 servers/hgfs/write.c diff --git a/etc/system.conf b/etc/system.conf index a8567c11b..5abdc954c 100644 --- a/etc/system.conf +++ b/etc/system.conf @@ -302,6 +302,22 @@ service isofs uid 0; }; +service hgfs +{ + system + TIMES # 25 + GETINFO # 26 + SAFECOPYFROM # 31 + SAFECOPYTO # 32 + SETGRANT # 34 + PROFBUF # 38 + SYSCTL # 44 + ; + ipc + SYSTEM PM VFS RS VM + ; +}; + service printer { io 378:4 # LPT1 diff --git a/man/man8/hgfs.8 b/man/man8/hgfs.8 new file mode 100644 index 000000000..59cbbe0e0 --- /dev/null +++ b/man/man8/hgfs.8 @@ -0,0 +1,76 @@ +.TH HGFS 8 +.SH NAME +hgfs \- Host/Guest File System server +.SH SYNOPSIS +\fBmount \-t hgfs \fR[\fB\-r\fR] [\fB\-o \fIoptions\fR] \fBnone \fImountpoint +.SH DESCRIPTION +The Host/Guest File System (HGFS) server allows one to mount +VMware Shared Folders as a file system. This makes it possible to access +selected portions of the VMware host file system when MINIX is run as a +VMware guest. +.PP +The above mount command will mount the hgfs file system onto the directory +\fImountpoint\fR. The \fB\-r\fR mount option makes the file system read-only; +note that shared folders may independently have been configured as read-only +on the VMware host. The \fIoptions\fR field is a string consisting of +comma-delimited \fIkey\fR or \fIkey\fB=\fIvalue\fR options. The following +options are supported. +.TP 4 +\fBprefix=\fIpath\fR +This option sets a path prefix that will be prepended to all file system +operations on the host system. When mounted without a prefix (the default), +the root directory of an HGFS mount will contain all the names of the +available shares. The prefix option can be used to mount one of those shares, +by specifying its name as the prefix. Multi-component path prefixes are +supported as well. +.TP +\fBuid=\fInumber\fR +This sets the user ID used for all the files and directories in the file +system, allowing a non-root user to be the owner. The value must be specified +as a decimal number. +The default is root (the number \fB0\fR). +.TP +\fBgid=\fInumber\fR +Likewise, sets the group ID for all files and directories. +The default is operator (the number \fB0\fR). +.TP +\fBfmask=\fInumber\fR +This option sets the file permission mask of regular files. It is specified as +an octal number. For example, a value of \fB600\fR makes all files readable and +writable by the owning user (see the "\fBuid\fR" option). +The default is \fB755\fR. +.TP +\fBdmask=\fInumber\fR +Likewise, sets the file permission mask of directories. +The default is also \fB755\fR. +.TP +\fBicase\fR +This option tells HGFS to treat names as case-insensitive. +.TP +\fBnoicase\fR +This option, set by default, reverts the effect of an earlier specified +"\fBicase\fR" option. +.SH EXAMPLES +.TP 20 +.B mount \-t hgfs none /mnt +# Mount the entire shared folders tree on \fI/mnt\fR +.TP 20 +.B mount \-t hgfs \-o prefix=shared,uid=20,fmask=644,icase none /usr/shared +Mount the "\fIshared\fR" shared folder on \fI/usr/shared\fR +.SH LIMITATIONS +HGFS uses the first and original version of the VMware Shared Folders protocol +to talk to the VMware host. That means that HGFS should work with all VMware +products that support shared folders. However, this also imposes a number of +limitations. For example, the first version of the protocol supports only +regular files and directories (and not links), and does not have support for +automatic case sensitivity handling. +.PP +Some file system operations may not behave as expected, because the behavior +of HGFS is determined largely by the host. Other file system operations +(in particular, using directories as mountpoints) are not implemented, +because the file system structure as perceived by HGFS may change arbitrarily +at any time, due to modifications on the host side. +.SH "SEE ALSO" +.BR mount (1) +.SH AUTHOR +David van Moolenbroek diff --git a/servers/Makefile b/servers/Makefile index 2b5a979b8..3da04d36c 100644 --- a/servers/Makefile +++ b/servers/Makefile @@ -20,6 +20,7 @@ all install depend clean: cd ./mfs && $(MAKE) $@ cd ./pfs && $(MAKE) $@ cd ./iso9660fs && $(MAKE) $@ + cd ./hgfs && $(MAKE) $@ cd ./rs && $(MAKE) $@ cd ./ds && $(MAKE) $@ cd ./is && $(MAKE) $@ diff --git a/servers/hgfs/Makefile b/servers/hgfs/Makefile new file mode 100644 index 000000000..109fdf647 --- /dev/null +++ b/servers/hgfs/Makefile @@ -0,0 +1,34 @@ +# Makefile for VMware Host/Guest File System (HGFS) server +SERVER=hgfs + +LIBHGFSDIR=./libhgfs +LIBHGFS=$(LIBHGFSDIR)/libhgfs.a + +DEST=/sbin/$(SERVER) +LIBS=-lsys -L$(LIBHGFSDIR) -lhgfs + +OBJ=dentry.o handle.o inode.o link.o lookup.o main.o \ + misc.o mount.o name.o optset.o path.o read.o \ + stat.o table.o util.o verify.o write.o + +all build: $(SERVER) + +$(SERVER): $(LIBHGFS) $(OBJ) + $(CC) -o $@ $(LDFLAGS) $(OBJ) $(LIBS) + +$(LIBHGFS): + cd $(LIBHGFSDIR) && $(MAKE) + +install: $(SERVER) + install -c $(SERVER) $(DEST) + +clean: + cd $(LIBHGFSDIR) && $(MAKE) $@ + rm -f $(SERVER) $(OBJ) + +depend: + cd $(LIBHGFSDIR) && $(MAKE) $@ + mkdep "$(CC) -E $(CPPFLAGS)" *.c > .depend + +# Include generated dependencies. +include .depend diff --git a/servers/hgfs/const.h b/servers/hgfs/const.h new file mode 100644 index 000000000..9d0e7677c --- /dev/null +++ b/servers/hgfs/const.h @@ -0,0 +1,13 @@ + +/* Number of inodes. */ +/* The following number must not exceed 16. The i_num field is only a short. */ +#define NUM_INODE_BITS 8 +#define NUM_INODES ((1 << NUM_INODE_BITS) - 1) + +/* Number of entries in the name hashtable. */ +#define NUM_HASH_SLOTS 1023 + +/* Arbitrary block size constant returned by fstatfs. du(1) uses this. + * Also used by getdents. This is not the actual HGFS data transfer unit size. + */ +#define BLOCK_SIZE 4096 diff --git a/servers/hgfs/dentry.c b/servers/hgfs/dentry.c new file mode 100644 index 000000000..6926498e9 --- /dev/null +++ b/servers/hgfs/dentry.c @@ -0,0 +1,194 @@ +/* This file contains directory entry management and the name lookup hashtable. + * + * The entry points into this file are: + * init_dentry initialize the directory entry name lookup hashtable + * lookup_dentry find an inode based on parent directory and name + * add_dentry add an inode as directory entry to a parent directory + * del_dentry delete an inode from its parent directory + * + * Created: + * April 2009 (D.C. van Moolenbroek) + */ + +#include "inc.h" + +PRIVATE LIST_HEAD(hash_head, inode) hash_table[NUM_HASH_SLOTS]; + +FORWARD _PROTOTYPE( void del_one_dentry, (struct inode *ino) ); +FORWARD _PROTOTYPE( unsigned int hash_dentry, (struct inode *parent, + char *name) ); + +/*===========================================================================* + * init_dentry * + *===========================================================================*/ +PUBLIC void init_dentry() +{ +/* Initialize the names hashtable. + */ + int i; + + for (i = 0; i < NUM_HASH_SLOTS; i++) + LIST_INIT(&hash_table[i]); +} + +/*===========================================================================* + * lookup_dentry * + *===========================================================================*/ +PUBLIC struct inode *lookup_dentry(parent, name) +struct inode *parent; +char *name; +{ +/* Given a directory inode and a component name, look up the inode associated + * with that directory entry. Return the inode (with increased reference + * count) if found, or NIL_INODE otherwise. + */ + struct inode *ino; + unsigned int slot; + + assert(IS_DIR(parent)); + + slot = hash_dentry(parent, name); + + LIST_FOREACH(ino, &hash_table[slot], i_hash) { + if (compare_name(ino->i_name, name) == TRUE) + break; + } + + if (ino == NIL_INODE) + return NIL_INODE; + + get_inode(ino); + + return ino; +} + +/*===========================================================================* + * add_dentry * + *===========================================================================*/ +PUBLIC void add_dentry(parent, name, ino) +struct inode *parent; +char *name; +struct inode *ino; +{ +/* Add an entry to a parent inode, in the form of a new inode, with the given + * name. An entry with this name must not already exist. + */ + unsigned int slot; + + assert(IS_DIR(parent)); + assert(parent->i_ref > 0); + assert(ino->i_ref > 0); + assert(name[0]); + assert(strlen(name) <= NAME_MAX); + + link_inode(parent, ino); + + strcpy(ino->i_name, name); + + /* hash_add(ino); */ + slot = hash_dentry(parent, ino->i_name); + LIST_INSERT_HEAD(&hash_table[slot], ino, i_hash); +} + +/*===========================================================================* + * del_one_dentry * + *===========================================================================*/ +PRIVATE void del_one_dentry(ino) +struct inode *ino; +{ +/* This inode has become inaccessible by name. Disassociate it from its parent + * and remove it from the names hash table. + */ + + /* There can and must be exactly one root inode, so don't delete it! */ + if (IS_ROOT(ino)) + return; + + /* INUSE -> DELETED, CACHED -> FREE */ + + /* Remove the entry from the hashtable. + * Decrease parent's refcount, possibly adding it to the free list. + * Do not touch open handles. Do not add to the free list. + */ + + assert(ino->i_parent != NIL_INODE); + + /* hash_del(ino); */ + LIST_REMOVE(ino, i_hash); + + ino->i_name[0] = 0; + + unlink_inode(ino); +} + +/*===========================================================================* + * del_dentry * + *===========================================================================*/ +PUBLIC void del_dentry(ino) +struct inode *ino; +{ +/* Disassociate an inode from its parent, effectively deleting it. Recursively + * delete all its children as well, fragmenting the deleted branch into single + * inodes. + */ + LIST_HEAD(work_list, inode) work_list; + struct inode *child; + + del_one_dentry(ino); + + /* Quick way out: one directory entry that itself has no children. */ + if (!HAS_CHILDREN(ino)) + return; + + /* Recursively delete all children of the inode as well. + * Iterative version: this is potentially 128 levels deep. + */ + + LIST_INIT(&work_list); + LIST_INSERT_HEAD(&work_list, ino, i_next); + + do { + ino = LIST_FIRST(&work_list); + LIST_REMOVE(ino, i_next); + + assert(IS_DIR(ino)); + + while (!LIST_EMPTY(&ino->i_child)) { + child = LIST_FIRST(&ino->i_child); + LIST_REMOVE(child, i_next); + + del_one_dentry(child); + + if (HAS_CHILDREN(child)) + LIST_INSERT_HEAD(&work_list, child, i_next); + } + } while (!LIST_EMPTY(&work_list)); +} + +/*===========================================================================* + * hash_dentry * + *===========================================================================*/ +PRIVATE unsigned int hash_dentry(parent, name) +struct inode *parent; +char *name; +{ +/* Generate a hash value for a given name. Normalize the name first, so that + * different variations of the name will result in the same hash value. + */ + unsigned int val; + char buf[NAME_MAX+1], *p; + + dprintf(("HGFS: hash_dentry for '%s'\n", name)); + + normalize_name(buf, name); + + /* djb2 string hash algorithm, XOR variant */ + val = 5381; + for (p = buf; *p; p++) + val = ((val << 5) + val) ^ *p; + + /* Mix with inode number: typically, many file names occur in several + * different directories. + */ + return (val ^ parent->i_num) % NUM_HASH_SLOTS; +} diff --git a/servers/hgfs/glo.h b/servers/hgfs/glo.h new file mode 100644 index 000000000..23aa0941a --- /dev/null +++ b/servers/hgfs/glo.h @@ -0,0 +1,12 @@ + +#ifdef _TABLE +#undef EXTERN +#define EXTERN +#endif + +EXTERN message m_in; /* request message */ +EXTERN message m_out; /* reply message */ +EXTERN struct state state; /* global state */ +EXTERN struct opt opt; /* global options */ + +extern _PROTOTYPE( int (*call_vec[]), (void) ); diff --git a/servers/hgfs/handle.c b/servers/hgfs/handle.c new file mode 100644 index 000000000..41115d396 --- /dev/null +++ b/servers/hgfs/handle.c @@ -0,0 +1,78 @@ +/* This file contains open file and directory handle management functions. + * + * The entry points into this file are: + * get_handle open a handle for an inode and store the handle + * put_handle close any handles associated with an inode + * + * Created: + * April 2007 (D.C. van Moolenbroek) + */ + +#include "inc.h" + +#include + +/*===========================================================================* + * get_handle * + *===========================================================================*/ +PUBLIC int get_handle(ino) +struct inode *ino; +{ +/* Get an open file or directory handle for an inode. + */ + char path[PATH_MAX]; + int r; + + /* If we don't have a file handle yet, try to open the file now. */ + if (ino->i_flags & I_HANDLE) + return OK; + + if ((r = verify_inode(ino, path, NULL)) != OK) + return r; + + if (IS_DIR(ino)) { + r = hgfs_opendir(path, &ino->i_dir); + } + else { + if (!state.read_only) + r = hgfs_open(path, O_RDWR, 0, &ino->i_file); + + /* Protection or mount status might prevent us from writing. With the + * information that we have available, this is the best we can do.. + */ + if (state.read_only || r != OK) + r = hgfs_open(path, O_RDONLY, 0, &ino->i_file); + } + + if (r != OK) + return r; + + ino->i_flags |= I_HANDLE; + + return OK; +} + +/*===========================================================================* + * put_handle * + *===========================================================================*/ +PUBLIC void put_handle(ino) +struct inode *ino; +{ +/* Close an open file or directory handle associated with an inode. + */ + int r; + + if (!(ino->i_flags & I_HANDLE)) + return; + + /* We ignore any errors here, because we can't deal with them anyway. */ + if (IS_DIR(ino)) + r = hgfs_closedir(ino->i_dir); + else + r = hgfs_close(ino->i_file); + + if (r != OK) + printf("HGFS: put_handle: handle close operation returned %d\n", r); + + ino->i_flags &= ~I_HANDLE; +} diff --git a/servers/hgfs/inc.h b/servers/hgfs/inc.h new file mode 100644 index 000000000..01b77c660 --- /dev/null +++ b/servers/hgfs/inc.h @@ -0,0 +1,37 @@ + +#define _POSIX_SOURCE 1 /* for signal handling */ +#define _SYSTEM 1 /* for negative error values */ +#define _MINIX 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if DEBUG +#define dprintf(x) printf x +#else +#define dprintf(x) +#endif + +#include +#include +#include +#include +#include +#include + +#include "libhgfs/hgfs.h" + +#include "type.h" +#include "const.h" +#include "proto.h" +#include "glo.h" + +#include "inode.h" diff --git a/servers/hgfs/inode.c b/servers/hgfs/inode.c new file mode 100644 index 000000000..ee3b0edbd --- /dev/null +++ b/servers/hgfs/inode.c @@ -0,0 +1,299 @@ +/* This file deals with inode management. + * + * The entry points into this file are: + * init_inode initialize the inode table, return the root inode + * find_inode find an inode based on its inode number + * get_inode increase the reference count of an inode + * put_inode decrease the reference count of an inode + * link_inode link an inode as a directory entry to another inode + * unlink_inode unlink an inode from its parent directory + * get_free_inode return a free inode object + * have_free_inode check whether there is a free inode available + * have_used_inode check whether any inode is still in use + * do_putnode perform the PUTNODE file system call + * + * Created: + * April 2009 (D.C. van Moolenbroek) + */ + +#include "inc.h" + +PRIVATE TAILQ_HEAD(free_head, inode) free_list; + +/*===========================================================================* + * init_inode * + *===========================================================================*/ +PUBLIC struct inode *init_inode() +{ +/* Initialize inode table. Return the root inode. + */ + struct inode *ino; + unsigned int index; + + TAILQ_INIT(&free_list); + + dprintf(("HGFS: %d inodes, %d bytes each, equals %d bytes\n", + NUM_INODES, sizeof(struct inode), sizeof(inodes))); + + /* Mark all inodes except the root inode as free. */ + for (index = 1; index < NUM_INODES; index++) { + ino = &inodes[index]; + ino->i_parent = NIL_INODE; + LIST_INIT(&ino->i_child); + ino->i_num = index + 1; + ino->i_gen = (unsigned short)-1; /* aesthetics */ + ino->i_ref = 0; + ino->i_flags = 0; + TAILQ_INSERT_TAIL(&free_list, ino, i_free); + } + + /* Initialize and return the root inode. */ + ino = &inodes[0]; + ino->i_parent = ino; /* root inode is its own parent */ + LIST_INIT(&ino->i_child); + ino->i_num = ROOT_INODE_NR; + ino->i_gen = 0; /* unused by root node */ + ino->i_ref = 1; /* root inode is hereby in use */ + ino->i_flags = I_DIR; /* root inode is a directory */ + ino->i_name[0] = 0; /* root inode has empty name */ + + return ino; +} + +/*===========================================================================* + * find_inode * + *===========================================================================*/ +PUBLIC struct inode *find_inode(ino_nr) +ino_t ino_nr; +{ +/* Get an inode based on its inode number. Do not increase its reference count. + */ + struct inode *ino; + unsigned int index; + + /* Inode 0 (= index -1) is not a valid inode number. */ + index = INODE_INDEX(ino_nr); + if (index < 0) { + printf("HGFS: VFS passed invalid inode number!\n"); + + return NIL_INODE; + } + + assert(index < NUM_INODES); + + ino = &inodes[index]; + + /* Make sure the generation number matches. */ + if (INODE_GEN(ino_nr) != ino->i_gen) { + printf("HGFS: VFS passed outdated inode number!\n"); + + return NIL_INODE; + } + + /* The VFS/FS protocol only uses referenced inodes. */ + if (ino->i_ref == 0) + printf("HGFS: VFS passed unused inode!\n"); + + return ino; +} + +/*===========================================================================* + * get_inode * + *===========================================================================*/ +PUBLIC void get_inode(ino) +struct inode *ino; +{ +/* Increase the given inode's reference count. If both reference and link + * count were zero before, remove the inode from the free list. + */ + + dprintf(("HGFS: get_inode(%p) ['%s']\n", ino, ino->i_name)); + + /* (INUSE, CACHED) -> INUSE */ + + /* If this is the first reference, remove the node from the free list. */ + if (ino->i_ref == 0 && !HAS_CHILDREN(ino)) + TAILQ_REMOVE(&free_list, ino, i_free); + + ino->i_ref++; + + if (ino->i_ref == 0) + panic("HGFS", "inode reference count wrapped", NO_NUM); +} + +/*===========================================================================* + * put_inode * + *===========================================================================*/ +PUBLIC void put_inode(ino) +struct inode *ino; +{ +/* Decrease an inode's reference count. If this count has reached zero, close + * the inode's file handle, if any. If both reference and link count have + * reached zero, mark the inode as cached or free. + */ + + dprintf(("HGFS: put_inode(%p) ['%s']\n", ino, ino->i_name)); + + assert(ino != NIL_INODE); + assert(ino->i_ref > 0); + + ino->i_ref--; + + /* If there are still references to this inode, we're done here. */ + if (ino->i_ref > 0) + return; + + /* Close any file handle associated with this inode. */ + put_handle(ino); + + /* Only add the inode to the free list if there are also no links to it. */ + if (HAS_CHILDREN(ino)) + return; + + /* INUSE -> CACHED, DELETED -> FREE */ + + /* Add the inode to the head or tail of the free list, depending on whether + * it is also deleted (and therefore can never be reused as is). + */ + if (ino->i_parent == NIL_INODE) + TAILQ_INSERT_HEAD(&free_list, ino, i_free); + else + TAILQ_INSERT_TAIL(&free_list, ino, i_free); +} + +/*===========================================================================* + * link_inode * + *===========================================================================*/ +PUBLIC void link_inode(parent, ino) +struct inode *parent; +struct inode *ino; +{ +/* Link an inode to a parent. If both reference and link count were zero + * before, remove the inode from the free list. This function should only be + * called from add_dentry(). + */ + + /* This can never happen, right? */ + if (parent->i_ref == 0 && !HAS_CHILDREN(parent)) + TAILQ_REMOVE(&free_list, parent, i_free); + + LIST_INSERT_HEAD(&parent->i_child, ino, i_next); + + ino->i_parent = parent; +} + +/*===========================================================================* + * unlink_inode * + *===========================================================================*/ +PUBLIC void unlink_inode(ino) +struct inode *ino; +{ +/* Unlink an inode from its parent. If both reference and link count have + * reached zero, mark the inode as cached or free. This function should only + * be used from del_dentry(). + */ + struct inode *parent; + + parent = ino->i_parent; + + LIST_REMOVE(ino, i_next); + + if (parent->i_ref == 0 && !HAS_CHILDREN(parent)) { + if (parent->i_parent == NIL_INODE) + TAILQ_INSERT_HEAD(&free_list, parent, i_free); + else + TAILQ_INSERT_TAIL(&free_list, parent, i_free); + } + + ino->i_parent = NIL_INODE; +} + +/*===========================================================================* + * get_free_inode * + *===========================================================================*/ +PUBLIC struct inode *get_free_inode() +{ +/* Return a free inode object (with reference count 1), if available. + */ + struct inode *ino; + + /* [CACHED -> FREE,] FREE -> DELETED */ + + /* If there are no inodes on the free list, we cannot satisfy the request. */ + if (TAILQ_EMPTY(&free_list)) { + printf("HGFS: out of inodes!\n"); + + return NIL_INODE; + } + + ino = TAILQ_FIRST(&free_list); + TAILQ_REMOVE(&free_list, ino, i_free); + + assert(ino->i_ref == 0); + assert(!HAS_CHILDREN(ino)); + + /* If this was a cached inode, free it first. */ + if (ino->i_parent != NIL_INODE) + del_dentry(ino); + + assert(ino->i_parent == NIL_INODE); + + /* Initialize a subset of its fields */ + ino->i_gen++; + ino->i_ref = 1; + + return ino; +} + +/*===========================================================================* + * have_free_inode * + *===========================================================================*/ +PUBLIC int have_free_inode() +{ +/* Check whether there are any free inodes at the moment. Kind of lame, but + * this allows for easier error recovery in some places. + */ + + return !TAILQ_EMPTY(&free_list); +} + +/*===========================================================================* + * have_used_inode * + *===========================================================================*/ +PUBLIC int have_used_inode() +{ +/* Check whether any inodes are still in use, that is, any of the inodes have + * a reference count larger than zero. + */ + unsigned int index; + + for (index = 0; index < NUM_INODES; index++) + if (inodes[index].i_ref > 0) + return TRUE; + + return FALSE; +} + +/*===========================================================================* + * do_putnode * + *===========================================================================*/ +PUBLIC int do_putnode() +{ +/* Decrease an inode's reference count. + */ + struct inode *ino; + int count; + + if ((ino = find_inode(m_in.REQ_INODE_NR)) == NIL_INODE) + return EINVAL; + + count = m_in.REQ_COUNT; + + if (count <= 0 || count > ino->i_ref) return EINVAL; + + ino->i_ref -= count - 1; + + put_inode(ino); + + return OK; +} diff --git a/servers/hgfs/inode.h b/servers/hgfs/inode.h new file mode 100644 index 000000000..37c097810 --- /dev/null +++ b/servers/hgfs/inode.h @@ -0,0 +1,93 @@ +#ifndef _INODE_H +#define _INODE_H + +/* We cannot use inode number 0, so to be able to use bitmasks to combine + * inode and generation numbers, we have to use one fewer than the maximum of + * inodes possible by using NUM_INODE_BITS bits. + */ +#define NUM_INODES ((1 << NUM_INODE_BITS) - 1) + +/* The main portion of the inode array forms a fully linked tree, providing a + * cached partial view of what the server believes is on the host system. Each + * inode contains only a pointer to its parent and its path component name, so + * a path for an inode is constructed by walking up to the root. Inodes that + * are in use as directory for a child node must not be recycled; in this case, + * the i_child list is not empty. Naturally, inodes for which VFS holds a + * reference must also not be recycled; the i_ref count takes care of that. + * + * Multiple hard links to a single file do not exist; that is why an inode is + * also a directory entry (when in IN USE or CACHED state). Notifications about + * modifications on the host system are not part of the protocol, so sometimes + * the server may discover that some files do not exist anymore. In that case, + * they are marked as DELETED in the inode table. Such files may still be used + * because of open file handles, but cannot be referenced by path anymore. + * Unfortunately the HGFS v1 protocol is largely path-oriented, so even + * truncating a deleted file is not possible. This has been fixed in v2/v3, but + * we currently use the v1 protocol for VMware backwards compatibility reasons. + * + * An inode is REFERENCED iff it has a reference count > 0 *or* has children. + * An inode is LINKED IN iff it has a parent. + * + * An inode is IN USE iff it is REFERENCED and LINKED IN. + * An inode is CACHED iff it is NOT REFERENCED and LINKED IN. + * An inode is DELETED iff it is REFERENCED and NOT LINKED IN. + * An inode is FREE iff it is NOT REFERENCED and NOT LINKED IN. + * + * An inode may have an open file handle if it is IN USE or DELETED. + * An inode may have children if it is IN USE (and is a directory). + * An inode is in the names hashtable iff it is IN USE or CACHED. + * An inode is on the free list iff it is CACHED or FREE. + * + * - An IN USE inode becomes DELETED when it is either deleted explicitly, or + * when it has been determined to have become unreachable by path name on the + * host system (the verify_* functions take care of this). + * - An IN USE inode may become CACHED when there are no VFS references to it + * anymore (i_ref == 0), and it is not a directory with children. + * - A DELETED inode cannot have children, but may become FREE when there are + * also no VFS references to it anymore. + * - A CACHED inode may become IN USE when either i_ref or i_link is increased + * from zero. Practically, it will always be i_ref that gets increased, since + * i_link cannot be increased by VFS without having a reference to the inode. + * - A CACHED or FREE inode may be reused for other purposes at any time. + */ + +EXTERN struct inode { + struct inode *i_parent; /* parent inode pointer */ + LIST_HEAD(child_head, inode) i_child; /* child inode anchor */ + LIST_ENTRY(inode) i_next; /* sibling inode chain entry */ + LIST_ENTRY(inode) i_hash; /* hashtable chain entry */ + unsigned short i_num; /* inode number for quick reference */ + unsigned short i_gen; /* inode generation number */ + unsigned short i_ref; /* VFS reference count */ + unsigned short i_flags; /* any combination of I_* flags */ + union { + TAILQ_ENTRY(inode) u_free; /* free list chain entry */ + hgfs_file_t u_file; /* handle to open HGFS file */ + hgfs_dir_t u_dir; /* handle to open HGFS directory */ + } i_u; + char i_name[NAME_MAX+1]; /* entry name in parent directory */ +} inodes[NUM_INODES]; + +#define i_free i_u.u_free +#define i_file i_u.u_file +#define i_dir i_u.u_dir + +#define NIL_INODE ((struct inode *)NULL) + +#define I_DIR 0x01 /* this inode represents a directory */ +#define I_HANDLE 0x02 /* this inode has an open handle */ + +/* warning: the following line is not a proper macro */ +#define INODE_NR(i) (((i)->i_gen << NUM_INODE_BITS) | (i)->i_num) +#define INODE_INDEX(n) (((n) & ((1 << NUM_INODE_BITS) - 1)) - 1) +#define INODE_GEN(n) (((n) >> NUM_INODE_BITS) & 0xffff) + +#define ROOT_INODE_NR 1 + +#define IS_DIR(i) ((i)->i_flags & I_DIR) +#define IS_ROOT(i) ((i)->i_num == ROOT_INODE_NR) +#define HAS_CHILDREN(i) (!LIST_EMPTY(&(i)->i_child)) + +#define MODE_TO_DIRFLAG(m) (S_ISDIR(m) ? I_DIR : 0) + +#endif /* _INODE_H */ diff --git a/servers/hgfs/libhgfs/Makefile b/servers/hgfs/libhgfs/Makefile new file mode 100644 index 000000000..302d767e7 --- /dev/null +++ b/servers/hgfs/libhgfs/Makefile @@ -0,0 +1,26 @@ +# Makefile for HGFS library +LIBNAME=libhgfs.a + +OBJ=backdoor.o attr.o channel.o dir.o error.o file.o \ + link.o misc.o path.o rpc.o time.o + +AR=ar +GAS2ACK=gas2ack + +all build: $(LIBNAME) + +$(LIBNAME): $(OBJ) + $(AR) cr $@ $(OBJ) + +backdoor.o: backdoor.S + $(GAS2ACK) $< $@.s + $(CC) $(CFLAGS) -c -o $@ $@.s + +clean distclean: + rm -f $(LIBNAME) $(OBJ) *.o.s + +depend: + mkdep "$(CC) -E $(CPPFLAGS)" *.c > .depend + +# Include generated dependencies. +include .depend diff --git a/servers/hgfs/libhgfs/attr.c b/servers/hgfs/libhgfs/attr.c new file mode 100644 index 000000000..12fe0e733 --- /dev/null +++ b/servers/hgfs/libhgfs/attr.c @@ -0,0 +1,86 @@ +/* Part of libhgfs - (c) 2009, D.C. van Moolenbroek */ + +#include "inc.h" + +#include + +/*===========================================================================* + * attr_get * + *===========================================================================*/ +PUBLIC void attr_get(attr) +struct hgfs_attr *attr; +{ +/* Get attribute information from the RPC buffer, storing the requested parts + * in the given attr structure. + */ + mode_t mode; + u32_t size_lo, size_hi; + + mode = (RPC_NEXT32) ? S_IFDIR : S_IFREG; + + size_lo = RPC_NEXT32; + size_hi = RPC_NEXT32; + if (attr->a_mask & HGFS_ATTR_SIZE) + attr->a_size = make64(size_lo, size_hi); + + time_get((attr->a_mask & HGFS_ATTR_CRTIME) ? &attr->a_crtime : NULL); + time_get((attr->a_mask & HGFS_ATTR_ATIME) ? &attr->a_atime : NULL); + time_get((attr->a_mask & HGFS_ATTR_MTIME) ? &attr->a_mtime : NULL); + time_get((attr->a_mask & HGFS_ATTR_CTIME) ? &attr->a_ctime : NULL); + + mode |= HGFS_PERM_TO_MODE(RPC_NEXT8); + if (attr->a_mask & HGFS_ATTR_MODE) attr->a_mode = mode; +} + +/*===========================================================================* + * hgfs_getattr * + *===========================================================================*/ +PUBLIC int hgfs_getattr(path, attr) +char *path; +struct hgfs_attr *attr; +{ +/* Get selected attributes of a file by path name. + */ + int r; + + RPC_REQUEST(HGFS_REQ_GETATTR); + + path_put(path); + + if ((r = rpc_query()) != OK) + return r; + + attr_get(attr); + + return OK; +} + +/*===========================================================================* + * hgfs_setattr * + *===========================================================================*/ +PUBLIC int hgfs_setattr(path, attr) +char *path; +struct hgfs_attr *attr; +{ +/* Set selected attributes of a file by path name. + */ + + RPC_REQUEST(HGFS_REQ_SETATTR); + + RPC_NEXT8 = (attr->a_mask & HGFS_ATTR_ALL); + + RPC_NEXT32 = !!(S_ISDIR(attr->a_mode)); + RPC_NEXT32 = ex64lo(attr->a_size); + RPC_NEXT32 = ex64hi(attr->a_size); + + time_put((attr->a_mask & HGFS_ATTR_CRTIME) ? &attr->a_crtime : NULL); + time_put((attr->a_mask & HGFS_ATTR_ATIME) ? &attr->a_atime : NULL); + time_put((attr->a_mask & HGFS_ATTR_MTIME) ? &attr->a_mtime : NULL); + time_put((attr->a_mask & HGFS_ATTR_CTIME) ? &attr->a_ctime : NULL); + + RPC_NEXT8 = HGFS_MODE_TO_PERM(attr->a_mode); + + path_put(path); + + return rpc_query(); +} diff --git a/servers/hgfs/libhgfs/backdoor.S b/servers/hgfs/libhgfs/backdoor.S new file mode 100644 index 000000000..870419d3c --- /dev/null +++ b/servers/hgfs/libhgfs/backdoor.S @@ -0,0 +1,106 @@ +# Part of libhgfs - (c) 2009, D.C. van Moolenbroek + +.globl __libhgfs_backdoor +.globl __libhgfs_backdoor_in +.globl __libhgfs_backdoor_out + +.text + + MAGIC = 0x564D5868 + BD_PORT = 0x5658 + IO_PORT = 0x5659 + +.balign 16 +__libhgfs_backdoor: + pushl %ebx + pushl %esi + pushl %edi + pushl %ebp + movl 4+16(%esp), %ebp + movl $MAGIC, %eax + movl 4(%ebp), %ebx + movl 8(%ebp), %ecx + movl 12(%ebp), %edx + movw $BD_PORT, %dx + movl 16(%ebp), %esi + movl 20(%ebp), %edi + inl %dx + movl %eax, (%ebp) + movl %ebx, 4(%ebp) + movl %ecx, 8(%ebp) + movl %edx, 12(%ebp) + movl %esi, 16(%ebp) + movl %edi, 20(%ebp) + popl %ebp + popl %edi + popl %esi + popl %ebx + ret + +.balign 16 +__libhgfs_backdoor_in: + pushl %ebx + pushl %esi + pushl %edi + pushl %ebp + movl 4+16(%esp), %eax + movl 4(%eax), %ebx + movl 8(%eax), %ecx + movl 12(%eax), %edx + movw $IO_PORT, %dx + movl 16(%eax), %esi + movl 20(%eax), %edi + movl 24(%eax), %ebp + movl $MAGIC, %eax + cld + repe; insb + pushl %eax + movl 4+20(%esp), %eax + movl %ebx, 4(%eax) + movl %ecx, 8(%eax) + movl %edx, 12(%eax) + movl %esi, 16(%eax) + movl %edi, 20(%eax) + movl %ebp, 24(%eax) + popl %ebx + movl %ebx, (%eax) + movl (%eax), %eax + popl %ebp + popl %edi + popl %esi + popl %ebx + ret + +.balign 16 +__libhgfs_backdoor_out: + pushl %ebx + pushl %esi + pushl %edi + pushl %ebp + movl 4+16(%esp), %eax + movl 4(%eax), %ebx + movl 8(%eax), %ecx + movl 12(%eax), %edx + movw $IO_PORT, %dx + movl 16(%eax), %esi + movl 20(%eax), %edi + movl 24(%eax), %ebp + movl $MAGIC, %eax + cld + repe; outsb + pushl %eax + movl 4+20(%esp), %eax + movl %ebx, 4(%eax) + movl %ecx, 8(%eax) + movl %edx, 12(%eax) + movl %esi, 16(%eax) + movl %edi, 20(%eax) + movl %ebp, 24(%eax) + popl %ebx + movl %ebx, (%eax) + movl (%eax), %eax + popl %ebp + popl %edi + popl %esi + popl %ebx + ret diff --git a/servers/hgfs/libhgfs/channel.c b/servers/hgfs/libhgfs/channel.c new file mode 100644 index 000000000..8ed97f4c0 --- /dev/null +++ b/servers/hgfs/libhgfs/channel.c @@ -0,0 +1,146 @@ +/* Part of libhgfs - (c) 2009, D.C. van Moolenbroek */ + +#include "inc.h" + +#define CMD_XFER 0x1E /* vmware backdoor transfer command */ + +enum { + XFER_OPEN, /* open transfer channel */ + XFER_SENDLEN, /* specify length of data to send */ + XFER_SEND, /* send data */ + XFER_RECVLEN, /* get length of data to receive */ + XFER_RECV, /* receive data */ + XFER_RECVACK, /* acknowledge receipt of data */ + XFER_CLOSE /* close transfer channel */ +}; + +#define STATUS(p) (HIWORD((p)[2]) & 0xff) + +/*===========================================================================* + * channel_open * + *===========================================================================*/ +PUBLIC int channel_open(ch, type) +struct channel *ch; /* struct describing the new channel */ +u32_t type; /* channel type: CH_IN or CH_OUT */ +{ +/* Open a new backdoor channel. Upon success, the given channel structure will + * be filled with information and can be used in subsequent channel calls. + * Return OK on success, or a negative error code on error. + */ + u32_t ptr[6]; + + ptr[1] = type | 0x80000000; + ptr[2] = MAKELONG(CMD_XFER, XFER_OPEN); + + backdoor(ptr); + + if ((STATUS(ptr) & 1) == 0) return EIO; + + ch->id = HIWORD(ptr[3]); + ch->cookie1 = ptr[4]; + ch->cookie2 = ptr[5]; + + return OK; +} + +/*===========================================================================* + * channel_close * + *===========================================================================*/ +PUBLIC void channel_close(ch) +struct channel *ch; /* backdoor channel to close */ +{ +/* Close a previously opened backdoor channel. + */ + u32_t ptr[6]; + + ptr[2] = MAKELONG(CMD_XFER, XFER_CLOSE); + ptr[3] = MAKELONG(0, ch->id); + ptr[4] = ch->cookie1; + ptr[5] = ch->cookie2; + + backdoor(ptr); +} + +/*===========================================================================* + * channel_send * + *===========================================================================*/ +PUBLIC int channel_send(ch, buf, len) +struct channel *ch; /* backdoor channel to send to */ +char *buf; /* buffer to send data from */ +int len; /* size of the data to send */ +{ +/* Receive data over a backdoor channel. Return OK on success, or a negative + * error code on error. + */ + u32_t ptr[7]; + + ptr[1] = len; + ptr[2] = MAKELONG(CMD_XFER, XFER_SENDLEN); + ptr[3] = MAKELONG(0, ch->id); + ptr[4] = ch->cookie1; + ptr[5] = ch->cookie2; + + backdoor(ptr); + + if ((STATUS(ptr) & 1) == 0) return EIO; + + if (len == 0) return OK; + + ptr[1] = MAKELONG(0, 1); + ptr[2] = len; + ptr[3] = MAKELONG(0, ch->id); + ptr[4] = (u32_t)buf; + ptr[5] = ch->cookie2; + ptr[6] = ch->cookie1; + + backdoor_out(ptr); + + return OK; +} + +/*===========================================================================* + * channel_recv * + *===========================================================================*/ +PUBLIC int channel_recv(ch, buf, max) +struct channel *ch; /* backdoor channel to receive from */ +char *buf; /* buffer to receive data into */ +int max; /* size of the buffer */ +{ +/* Receive data on a backdoor channel. Return the number of bytes received, or + * a negative error code on error. + */ + u32_t ptr[7]; + int len; + + ptr[2] = MAKELONG(CMD_XFER, XFER_RECVLEN); + ptr[3] = MAKELONG(0, ch->id); + ptr[4] = ch->cookie1; + ptr[5] = ch->cookie2; + + backdoor(ptr); + + if ((STATUS(ptr) & 0x81) == 0) return EIO; + + if ((len = ptr[1]) == 0 || (STATUS(ptr) & 3) == 1) return 0; + + if (len > max) return E2BIG; + + ptr[1] = MAKELONG(0, 1); + ptr[2] = len; + ptr[3] = MAKELONG(0, ch->id); + ptr[4] = ch->cookie1; + ptr[5] = (u32_t)buf; + ptr[6] = ch->cookie2; + + backdoor_in(ptr); + + ptr[1] = 1; + ptr[2] = MAKELONG(CMD_XFER, XFER_RECVACK); + ptr[3] = MAKELONG(0, ch->id); + ptr[4] = ch->cookie1; + ptr[5] = ch->cookie2; + + backdoor(ptr); + + return len; +} diff --git a/servers/hgfs/libhgfs/const.h b/servers/hgfs/libhgfs/const.h new file mode 100644 index 000000000..7e4772481 --- /dev/null +++ b/servers/hgfs/libhgfs/const.h @@ -0,0 +1,62 @@ +/* Part of libhgfs - (c) 2009, D.C. van Moolenbroek */ + +/* Various macros used here and there */ +#define MAKELONG(a,b) ((a) | ((b) << 16)) +#define HIWORD(d) ((d) >> 16) +#define LOWORD(d) ((d) & 0xffff) +#define BYTES(a,b,c,d) ((a) | ((b) << 8) | ((c) << 16) | ((d) << 24)) + +/* Valid channel types for channel_open() */ +#define CH_IN BYTES('T', 'C', 'L', 'O') +#define CH_OUT BYTES('R', 'P', 'C', 'I') + +/* RPC constants */ +#define RPC_BUF_SIZE 6134 /* max size of RPC request */ +#define RPC_HDR_SIZE 10 /* RPC HGFS header size */ + +/* RPC macros. These NEED NOT be portable. VMware only does x86(-64) anyway. */ +/* ..all this because ACK can't pack structures :( */ +#define RPC_NEXT8 *(((u8_t*)(++rpc_ptr))-1) /* get/set next byte */ +#define RPC_NEXT16 *(((u16_t*)(rpc_ptr+=2))-1) /* get/set next short */ +#define RPC_NEXT32 *(((u32_t*)(rpc_ptr+=4))-1) /* get/set next long */ +#define RPC_LEN (rpc_ptr - rpc_buf) /* request length thus far */ +#define RPC_ADVANCE(n) rpc_ptr += n /* skip n bytes in buffer */ +#define RPC_PTR (rpc_ptr) /* pointer to next data */ +#define RPC_RESET rpc_ptr = rpc_buf /* start at beginning */ +#define RPC_REQUEST(r) \ + RPC_RESET; \ + RPC_NEXT8 = 'f'; \ + RPC_NEXT8 = ' '; \ + RPC_NEXT32 = 0; \ + RPC_NEXT32 = r; /* start a RPC request */ + +/* HGFS requests */ +enum { + HGFS_REQ_OPEN, + HGFS_REQ_READ, + HGFS_REQ_WRITE, + HGFS_REQ_CLOSE, + HGFS_REQ_OPENDIR, + HGFS_REQ_READDIR, + HGFS_REQ_CLOSEDIR, + HGFS_REQ_GETATTR, + HGFS_REQ_SETATTR, + HGFS_REQ_MKDIR, + HGFS_REQ_UNLINK, + HGFS_REQ_RMDIR, + HGFS_REQ_RENAME, + HGFS_REQ_QUERYVOL +}; + +/* HGFS open types */ +enum { + HGFS_OPEN_TYPE_O, + HGFS_OPEN_TYPE_OT, + HGFS_OPEN_TYPE_CO, + HGFS_OPEN_TYPE_C, + HGFS_OPEN_TYPE_COT +}; + +/* HGFS mode/perms conversion macros */ +#define HGFS_MODE_TO_PERM(m) (((m) & S_IRWXU) >> 6) +#define HGFS_PERM_TO_MODE(p) (((p) << 6) & S_IRWXU) diff --git a/servers/hgfs/libhgfs/dir.c b/servers/hgfs/libhgfs/dir.c new file mode 100644 index 000000000..1f0081b9a --- /dev/null +++ b/servers/hgfs/libhgfs/dir.c @@ -0,0 +1,71 @@ +/* Part of libhgfs - (c) 2009, D.C. van Moolenbroek */ + +#include "inc.h" + +/*===========================================================================* + * hgfs_opendir * + *===========================================================================*/ +PUBLIC int hgfs_opendir(path, handle) +char *path; +hgfs_dir_t *handle; +{ +/* Open a directory. Store a directory handle upon success. + */ + int r; + + RPC_REQUEST(HGFS_REQ_OPENDIR); + + path_put(path); + + if ((r = rpc_query()) != OK) + return r; + + *handle = (hgfs_dir_t)RPC_NEXT32; + + return OK; +} + +/*===========================================================================* + * hgfs_readdir * + *===========================================================================*/ +PUBLIC int hgfs_readdir(handle, index, buf, size, attr) +hgfs_dir_t handle; +unsigned int index; +char *buf; +size_t size; +struct hgfs_attr *attr; +{ +/* Read a directory entry from an open directory, using a zero-based index + * number. Upon success, the resulting path name is stored in the given buffer + * and the given attribute structure is filled selectively as requested. Upon + * error, the contents of the path buffer and attribute structure are + * undefined. + */ + int r; + + RPC_REQUEST(HGFS_REQ_READDIR); + RPC_NEXT32 = (u32_t)handle; + RPC_NEXT32 = index; + + if ((r = rpc_query()) != OK) + return r; + + attr_get(attr); + + return path_get(buf, size); +} + +/*===========================================================================* + * hgfs_closedir * + *===========================================================================*/ +PUBLIC int hgfs_closedir(handle) +hgfs_dir_t handle; +{ +/* Close an open directory. + */ + + RPC_REQUEST(HGFS_REQ_CLOSEDIR); + RPC_NEXT32 = (u32_t)handle; + + return rpc_query(); +} diff --git a/servers/hgfs/libhgfs/error.c b/servers/hgfs/libhgfs/error.c new file mode 100644 index 000000000..af38fd9d2 --- /dev/null +++ b/servers/hgfs/libhgfs/error.c @@ -0,0 +1,40 @@ +/* Part of libhgfs - (c) 2009, D.C. van Moolenbroek */ + +#include "inc.h" + +/* Not sure if all of these occur in the HGFS v1 protocol, but at least some + * that weren't in the original protocol, are being returned now. + */ +#define NERRS 16 +PRIVATE int error_map[NERRS] = { + OK, /* no error */ + ENOENT, /* no such file/directory */ + EBADF, /* invalid handle */ + EPERM, /* operation not permitted */ + EEXIST, /* file already exists */ + ENOTDIR, /* not a directory */ + ENOTEMPTY, /* directory not empty */ + EIO, /* protocol error */ + EACCES, /* access denied */ + EINVAL, /* invalid name */ + EIO, /* generic error */ + EIO, /* sharing violation */ + ENOSPC, /* no space */ + ENOSYS, /* operation not supported */ + ENAMETOOLONG, /* name too long */ + EINVAL, /* invalid parameter */ +}; + +/*===========================================================================* + * error_convert * + *===========================================================================*/ +PUBLIC int error_convert(err) +int err; +{ +/* Convert a HGFS error into an errno error code. + */ + + if (err < 0 || err >= NERRS) return EIO; + + return error_map[err]; +} diff --git a/servers/hgfs/libhgfs/file.c b/servers/hgfs/libhgfs/file.c new file mode 100644 index 000000000..24bdbd157 --- /dev/null +++ b/servers/hgfs/libhgfs/file.c @@ -0,0 +1,182 @@ +/* Part of libhgfs - (c) 2009, D.C. van Moolenbroek */ + +#include "inc.h" + +#include +#include + +/*===========================================================================* + * hgfs_open * + *===========================================================================*/ +PUBLIC int hgfs_open(path, flags, mode, handle) +char *path; /* path name to open */ +int flags; /* open flags to use */ +int mode; /* mode to create (user bits only) */ +hgfs_file_t *handle; /* place to store resulting handle */ +{ +/* Open a file. Store a file handle upon success. + */ + int r, type; + + /* We could implement this, but that means we would have to start tracking + * open files in order to associate data with them. Rather not. + */ + if (flags & O_APPEND) return EINVAL; + + if (flags & O_CREAT) { + if (flags & O_EXCL) type = HGFS_OPEN_TYPE_C; + else if (flags & O_TRUNC) type = HGFS_OPEN_TYPE_COT; + else type = HGFS_OPEN_TYPE_CO; + } else { + if (flags & O_TRUNC) type = HGFS_OPEN_TYPE_OT; + else type = HGFS_OPEN_TYPE_O; + } + + RPC_REQUEST(HGFS_REQ_OPEN); + RPC_NEXT32 = (flags & O_ACCMODE); + RPC_NEXT32 = type; + RPC_NEXT8 = HGFS_MODE_TO_PERM(mode); + + path_put(path); + + if ((r = rpc_query()) != OK) + return r; + + *handle = (hgfs_file_t)RPC_NEXT32; + + return OK; +} + +/*===========================================================================* + * hgfs_read * + *===========================================================================*/ +PUBLIC int hgfs_read(handle, buf, size, off) +hgfs_file_t handle; /* handle to open file */ +char *buf; /* data buffer or NULL */ +size_t size; /* maximum number of bytes to read */ +u64_t off; /* file offset */ +{ +/* Read from an open file. Upon success, return the number of bytes read. + */ + int r, len, max; + + RPC_REQUEST(HGFS_REQ_READ); + RPC_NEXT32 = (u32_t)handle; + RPC_NEXT32 = ex64lo(off); + RPC_NEXT32 = ex64hi(off); + + max = RPC_BUF_SIZE - RPC_LEN - sizeof(u32_t); + RPC_NEXT32 = (size < max) ? size : max; + + if ((r = rpc_query()) != OK) + return r; + + len = RPC_NEXT32; + if (len > max) len = max; /* sanity check */ + + /* Only copy out data if we're requested to do so. */ + if (buf != NULL) + memcpy(buf, RPC_PTR, len); + + return len; +} + +/*===========================================================================* + * hgfs_write * + *===========================================================================*/ +PUBLIC int hgfs_write(handle, buf, len, off, append) +hgfs_file_t handle; /* handle to open file */ +const char *buf; /* data buffer or NULL */ +size_t len; /* number of bytes to write */ +u64_t off; /* file offset */ +int append; /* if set, append to file (ignore offset) */ +{ +/* Write to an open file. Upon success, return the number of bytes written. + */ + int r; + + RPC_REQUEST(HGFS_REQ_WRITE); + RPC_NEXT32 = (u32_t)handle; + + if (append) { + RPC_NEXT8 = 1; + RPC_NEXT32 = 0; + RPC_NEXT32 = 0; + } + else { + RPC_NEXT8 = 0; + RPC_NEXT32 = ex64lo(off); + RPC_NEXT32 = ex64hi(off); + } + + RPC_NEXT32 = len; + + /* Only copy in data if we're requested to do so. */ + if (buf != NULL) + memcpy(RPC_PTR, buf, len); + RPC_ADVANCE(len); + + if ((r = rpc_query()) != OK) + return r; + + return RPC_NEXT32; +} + +/*===========================================================================* + * hgfs_close * + *===========================================================================*/ +PUBLIC int hgfs_close(handle) +hgfs_file_t handle; /* handle to open file */ +{ +/* Close an open file. + */ + + RPC_REQUEST(HGFS_REQ_CLOSE); + RPC_NEXT32 = (u32_t)handle; + + return rpc_query(); +} + +/*===========================================================================* + * hgfs_readbuf * + *===========================================================================*/ +PUBLIC size_t hgfs_readbuf(ptr) +char **ptr; +{ +/* Return information about the read buffer, for zero-copy purposes. Store a + * pointer to the first byte of the read buffer, and return the maximum data + * size. The results are static, but must only be used directly prior to a + * hgfs_read() call (with a NULL data buffer address). + */ + u32_t off; + + off = RPC_HDR_SIZE + sizeof(u32_t); + + RPC_RESET; + RPC_ADVANCE(off); + *ptr = RPC_PTR; + + return RPC_BUF_SIZE - off; +} + +/*===========================================================================* + * hgfs_writebuf * + *===========================================================================*/ +PUBLIC size_t hgfs_writebuf(ptr) +char **ptr; +{ +/* Return information about the write buffer, for zero-copy purposes. Store a + * pointer to the first byte of the write buffer, and return the maximum data + * size. The results are static, but must only be used immediately after a + * hgfs_write() call (with a NULL data buffer address). + */ + u32_t off; + + off = RPC_HDR_SIZE + sizeof(u32_t) + sizeof(u8_t) + sizeof(u32_t) * 3; + + RPC_RESET; + RPC_ADVANCE(off); + *ptr = RPC_PTR; + + return RPC_BUF_SIZE - off; +} diff --git a/servers/hgfs/libhgfs/glo.h b/servers/hgfs/libhgfs/glo.h new file mode 100644 index 000000000..aa1ae7fe0 --- /dev/null +++ b/servers/hgfs/libhgfs/glo.h @@ -0,0 +1,6 @@ +/* Part of libhgfs - (c) 2009, D.C. van Moolenbroek */ + +#define rpc_buf PREFIX(rpc_buf) +#define rpc_ptr PREFIX(rpc_ptr) +extern char rpc_buf[RPC_BUF_SIZE]; +extern char *rpc_ptr; diff --git a/servers/hgfs/libhgfs/hgfs.h b/servers/hgfs/libhgfs/hgfs.h new file mode 100644 index 000000000..1d5a0dd89 --- /dev/null +++ b/servers/hgfs/libhgfs/hgfs.h @@ -0,0 +1,63 @@ +/* Part of libhgfs - (c) 2009, D.C. van Moolenbroek */ + +#ifndef _HGFS_H +#define _HGFS_H + +#include +#include + +typedef void *hgfs_file_t; /* handle to open file */ +typedef void *hgfs_dir_t; /* handle to directory search */ + +struct hgfs_attr { + u32_t a_mask; /* which fields to retrieve/set */ + mode_t a_mode; /* file type and permissions */ + u64_t a_size; /* file size */ + time_t a_crtime; /* file creation time */ + time_t a_atime; /* file access time */ + time_t a_mtime; /* file modification time */ + time_t a_ctime; /* file change time */ +}; + +#define HGFS_ATTR_SIZE 0x01 /* get/set file size */ +#define HGFS_ATTR_CRTIME 0x02 /* get/set file creation time */ +#define HGFS_ATTR_ATIME 0x04 /* get/set file access time */ +#define HGFS_ATTR_MTIME 0x08 /* get/set file modification time */ +#define HGFS_ATTR_CTIME 0x10 /* get/set file change time */ +#define HGFS_ATTR_MODE 0x20 /* get/set file mode */ +#define HGFS_ATTR_ALL \ + (HGFS_ATTR_SIZE | HGFS_ATTR_CRTIME | HGFS_ATTR_ATIME | \ + HGFS_ATTR_MTIME | HGFS_ATTR_CTIME | HGFS_ATTR_MODE) + +_PROTOTYPE( int hgfs_init, (void) ); +_PROTOTYPE( void hgfs_cleanup, (void) ); + +_PROTOTYPE( int hgfs_enabled, (void) ); + +_PROTOTYPE( int hgfs_open, (char *path, int flags, int mode, + hgfs_file_t *handle) ); +_PROTOTYPE( int hgfs_read, (hgfs_file_t handle, char *buf, size_t size, + u64_t offset) ); +_PROTOTYPE( int hgfs_write, (hgfs_file_t handle, const char *buf, + size_t len, u64_t offset, int append) ); +_PROTOTYPE( int hgfs_close, (hgfs_file_t handle) ); + +_PROTOTYPE( size_t hgfs_readbuf, (char **ptr) ); +_PROTOTYPE( size_t hgfs_writebuf, (char **ptr) ); + +_PROTOTYPE( int hgfs_opendir, (char *path, hgfs_dir_t *handle) ); +_PROTOTYPE( int hgfs_readdir, (hgfs_dir_t handle, unsigned int index, + char *buf, size_t size, struct hgfs_attr *attr) ); +_PROTOTYPE( int hgfs_closedir, (hgfs_dir_t handle) ); + +_PROTOTYPE( int hgfs_getattr, (char *path, struct hgfs_attr *attr) ); +_PROTOTYPE( int hgfs_setattr, (char *path, struct hgfs_attr *attr) ); + +_PROTOTYPE( int hgfs_mkdir, (char *path, int mode) ); +_PROTOTYPE( int hgfs_unlink, (char *path) ); +_PROTOTYPE( int hgfs_rmdir, (char *path) ); +_PROTOTYPE( int hgfs_rename, (char *opath, char *npath) ); + +_PROTOTYPE( int hgfs_queryvol, (char *path, u64_t *free, u64_t *total) ); + +#endif /* _HGFS_H */ diff --git a/servers/hgfs/libhgfs/inc.h b/servers/hgfs/libhgfs/inc.h new file mode 100644 index 000000000..5471cd597 --- /dev/null +++ b/servers/hgfs/libhgfs/inc.h @@ -0,0 +1,19 @@ +/* Part of libhgfs - (c) 2009, D.C. van Moolenbroek */ + +#define _POSIX_SOURCE 1 /* need PATH_MAX */ +#define _SYSTEM 1 /* need negative error codes */ + +#include +#include + +#include +#include + +#include "hgfs.h" + +#define PREFIX(x) __libhgfs_##x + +#include "type.h" +#include "const.h" +#include "proto.h" +#include "glo.h" diff --git a/servers/hgfs/libhgfs/link.c b/servers/hgfs/libhgfs/link.c new file mode 100644 index 000000000..274dbb46d --- /dev/null +++ b/servers/hgfs/libhgfs/link.c @@ -0,0 +1,73 @@ +/* Part of libhgfs - (c) 2009, D.C. van Moolenbroek */ + +#include "inc.h" + +#include + +/*===========================================================================* + * hgfs_mkdir * + *===========================================================================*/ +PUBLIC int hgfs_mkdir(path, mode) +char *path; +int mode; +{ +/* Create a new directory. + */ + + RPC_REQUEST(HGFS_REQ_MKDIR); + RPC_NEXT8 = HGFS_MODE_TO_PERM(mode); + + path_put(path); + + return rpc_query(); +} + +/*===========================================================================* + * hgfs_unlink * + *===========================================================================*/ +PUBLIC int hgfs_unlink(path) +char *path; +{ +/* Delete a file. + */ + + RPC_REQUEST(HGFS_REQ_UNLINK); + + path_put(path); + + return rpc_query(); +} + +/*===========================================================================* + * hgfs_rmdir * + *===========================================================================*/ +PUBLIC int hgfs_rmdir(path) +char *path; +{ +/* Remove an empty directory. + */ + + RPC_REQUEST(HGFS_REQ_RMDIR); + + path_put(path); + + return rpc_query(); +} + +/*===========================================================================* + * hgfs_rename * + *===========================================================================*/ +PUBLIC int hgfs_rename(opath, npath) +char *opath; +char *npath; +{ +/* Rename a file or directory. + */ + + RPC_REQUEST(HGFS_REQ_RENAME); + + path_put(opath); + path_put(npath); + + return rpc_query(); +} diff --git a/servers/hgfs/libhgfs/misc.c b/servers/hgfs/libhgfs/misc.c new file mode 100644 index 000000000..1acda1a08 --- /dev/null +++ b/servers/hgfs/libhgfs/misc.c @@ -0,0 +1,73 @@ +/* Part of libhgfs - (c) 2009, D.C. van Moolenbroek */ + +#include "inc.h" + +/*===========================================================================* + * hgfs_init * + *===========================================================================*/ +PUBLIC int hgfs_init() +{ +/* Initialize the library. Return OK on success, or a negative error code + * otherwise. If EAGAIN is returned, shared folders are disabled; in that + * case, other operations may be tried (and possibly succeed). + */ + + time_init(); + + return rpc_open(); +} + +/*===========================================================================* + * hgfs_cleanup * + *===========================================================================*/ +PUBLIC void hgfs_cleanup() +{ +/* Clean up state. + */ + + rpc_close(); +} + +/*===========================================================================* + * hgfs_enabled * + *===========================================================================*/ +PUBLIC int hgfs_enabled() +{ +/* Check if shared folders are enabled. Return OK if so, EAGAIN if not, and + * another negative error code on error. + */ + + return rpc_test(); +} + +/*===========================================================================* + * hgfs_queryvol * + *===========================================================================*/ +PUBLIC int hgfs_queryvol(path, free, total) +char *path; +u64_t *free; +u64_t *total; +{ +/* Retrieve information about available and total volume space associated with + * a given path. + */ + u32_t lo, hi; + int r; + + RPC_REQUEST(HGFS_REQ_QUERYVOL); + + path_put(path); + + if ((r = rpc_query()) != OK) + return r; + + lo = RPC_NEXT32; + hi = RPC_NEXT32; + *free = make64(lo, hi); + + lo = RPC_NEXT32; + hi = RPC_NEXT32; + *total = make64(lo, hi); + + return OK; +} diff --git a/servers/hgfs/libhgfs/path.c b/servers/hgfs/libhgfs/path.c new file mode 100644 index 000000000..6a2a2a110 --- /dev/null +++ b/servers/hgfs/libhgfs/path.c @@ -0,0 +1,73 @@ +/* Part of libhgfs - (c) 2009, D.C. van Moolenbroek */ + +#include "inc.h" + +#include + +/*===========================================================================* + * path_put * + *===========================================================================*/ +PUBLIC void path_put(path) +char *path; +{ +/* Append the given path name in HGFS format to the RPC buffer. Truncate it + * if it is longer than PATH_MAX bytes. + */ + char *p, buf[PATH_MAX]; + int len; + + /* No leading slashes are allowed. */ + for (p = path; *p == '/'; p++); + + /* No double or tailing slashes, either. */ + for (len = 0; *p && len < sizeof(buf) - 1; len++) { + if (*p == '/') { + for (p++; *p == '/'; p++); + + if (!*p) break; + + buf[len] = 0; + } + else buf[len] = *p++; + } + + RPC_NEXT32 = len; + + memcpy(RPC_PTR, buf, len); + RPC_ADVANCE(len); + + RPC_NEXT8 = 0; +} + +/*===========================================================================* + * path_get * + *===========================================================================*/ +PUBLIC int path_get(path, max) +char *path; +int max; +{ +/* Retrieve a HGFS formatted path name from the RPC buffer. Returns EINVAL if + * the path name is invalid. Returns ENAMETOOLONG if the path name is too + * long. Returns OK on success. + */ + char *p, *q; + int n, len; + + n = len = RPC_NEXT32; + + if (len >= max) return ENAMETOOLONG; + + for (p = path, q = RPC_PTR; n--; p++, q++) { + /* We can not deal with a slash in a path component. */ + if (*q == '/') return EINVAL; + + if (*q == 0) *p = '/'; + else *p = *q; + } + + RPC_ADVANCE(len); + + *p = 0; + + return (RPC_NEXT8 != 0) ? EINVAL : OK; +} diff --git a/servers/hgfs/libhgfs/proto.h b/servers/hgfs/libhgfs/proto.h new file mode 100644 index 000000000..513a9358e --- /dev/null +++ b/servers/hgfs/libhgfs/proto.h @@ -0,0 +1,51 @@ +/* Part of libhgfs - (c) 2009, D.C. van Moolenbroek */ + +/* attr.c */ +#define attr_get PREFIX(attr_get) +_PROTOTYPE( void attr_get, (struct hgfs_attr *attr) ); + +/* backdoor.s */ +#define backdoor PREFIX(backdoor) +#define backdoor_in PREFIX(backdoor_in) +#define backdoor_out PREFIX(backdoor_out) +_PROTOTYPE( u32_t backdoor, (u32_t ptr[6]) ); +_PROTOTYPE( u32_t backdoor_in, (u32_t ptr[6]) ); +_PROTOTYPE( u32_t backdoor_out, (u32_t ptr[6]) ); + +/* channel.c */ +#define channel_open PREFIX(channel_open) +#define channel_close PREFIX(channel_close) +#define channel_send PREFIX(channel_send) +#define channel_recv PREFIX(channel_recv) +_PROTOTYPE( int channel_open, (struct channel *ch, u32_t type) ); +_PROTOTYPE( void channel_close, (struct channel *ch) ); +_PROTOTYPE( int channel_send, (struct channel *ch, char *buf, int len) ); +_PROTOTYPE( int channel_recv, (struct channel *ch, char *buf, int max) ); + +/* error.c */ +#define error_convert PREFIX(error_convert) +_PROTOTYPE( int error_convert, (int err) ); + +/* path.c */ +#define path_put PREFIX(path_put) +#define path_get PREFIX(path_get) +_PROTOTYPE( void path_put, (char *path) ); +_PROTOTYPE( int path_get, (char *path, int max) ); + +/* rpc.c */ +#define rpc_open PREFIX(rpc_open) +#define rpc_query PREFIX(rpc_query) +#define rpc_test PREFIX(rpc_test) +#define rpc_close PREFIX(rpc_close) +_PROTOTYPE( int rpc_open, (void) ); +_PROTOTYPE( int rpc_query, (void) ); +_PROTOTYPE( int rpc_test, (void) ); +_PROTOTYPE( void rpc_close, (void) ); + +/* time.c */ +#define time_init PREFIX(time_init) +#define time_put PREFIX(time_put) +#define time_get PREFIX(time_get) +_PROTOTYPE( void time_init, (void) ); +_PROTOTYPE( void time_put, (time_t *timep) ); +_PROTOTYPE( void time_get, (time_t *timep) ); diff --git a/servers/hgfs/libhgfs/rpc.c b/servers/hgfs/libhgfs/rpc.c new file mode 100644 index 000000000..3345ea346 --- /dev/null +++ b/servers/hgfs/libhgfs/rpc.c @@ -0,0 +1,94 @@ +/* Part of libhgfs - (c) 2009, D.C. van Moolenbroek */ + +#include "inc.h" + +PUBLIC char rpc_buf[RPC_BUF_SIZE]; +PUBLIC char *rpc_ptr; + +PRIVATE struct channel rpc_chan; + +/*===========================================================================* + * rpc_open * + *===========================================================================*/ +PUBLIC int rpc_open() +{ +/* Open a HGFS RPC backdoor channel to the VMware host, and make sure that it + * is working. Return OK upon success, or a negative error code otherwise. + */ + int r; + + if ((r = channel_open(&rpc_chan, CH_OUT)) != OK) + return r; + + r = rpc_test(); + + if (r != OK && r != EAGAIN) + channel_close(&rpc_chan); + + return r; +} + +/*===========================================================================* + * rpc_query * + *===========================================================================*/ +PUBLIC int rpc_query() +{ +/* Send a HGFS RPC query over the backdoor channel. Return OK upon success, or + * a negative error code otherwise; EAGAIN is returned if shared folders are + * disabled. In general, we make the assumption that the sender (= VMware) + * speaks the protocol correctly. Hence, the callers of this function do not + * check for lengths. + */ + int r, len, id, err; + + len = RPC_LEN; + + /* A more robust version of this call could reopen the channel and + * retry the request upon low-level failure. + */ + r = channel_send(&rpc_chan, rpc_buf, len); + if (r < 0) return r; + + r = channel_recv(&rpc_chan, rpc_buf, sizeof(rpc_buf)); + if (r < 0) return r; + if (r < 2 || (len > 2 && r < 10)) return EIO; + + RPC_RESET; + + if (RPC_NEXT8 != '1') return EAGAIN; + if (RPC_NEXT8 != ' ') return EAGAIN; + + if (len <= 2) return OK; + + id = RPC_NEXT32; + err = RPC_NEXT32; + + return error_convert(err); +} + +/*===========================================================================* + * rpc_test * + *===========================================================================*/ +PUBLIC int rpc_test() +{ +/* Test whether HGFS communication is working. Return OK on success, EAGAIN if + * shared folders are disabled, or another negative error code upon error. + */ + + RPC_RESET; + RPC_NEXT8 = 'f'; + RPC_NEXT8 = ' '; + + return rpc_query(); +} + +/*===========================================================================* + * rpc_close * + *===========================================================================*/ +PUBLIC void rpc_close() +{ +/* Close the HGFS RPC backdoor channel. + */ + + channel_close(&rpc_chan); +} diff --git a/servers/hgfs/libhgfs/time.c b/servers/hgfs/libhgfs/time.c new file mode 100644 index 000000000..5daab9251 --- /dev/null +++ b/servers/hgfs/libhgfs/time.c @@ -0,0 +1,69 @@ +/* Part of libhgfs - (c) 2009, D.C. van Moolenbroek */ + +#include "inc.h" + +PRIVATE u64_t time_offset; + +/*===========================================================================* + * time_init * + *===========================================================================*/ +PUBLIC void time_init() +{ +/* Initialize the time conversion module. + */ + + /* Generate a 64-bit value for the offset to use in time conversion. The + * HGFS time format uses Windows' FILETIME standard, expressing time in + * 100ns-units since Jan 1, 1601 UTC. The value that is generated is + * 116444736000000000. + */ + /* FIXME: we currently do not take into account timezones. */ + time_offset = make64(3577643008UL, 27111902UL); +} + +/*===========================================================================* + * time_put * + *===========================================================================*/ +PUBLIC void time_put(timep) +time_t *timep; +{ +/* Store a UNIX timestamp pointed to by the given pointer onto the RPC buffer, + * in HGFS timestamp format. If a NULL pointer is given, store a timestamp of + * zero instead. + */ + u64_t hgfstime; + + if (timep != NULL) { + hgfstime = add64(mul64u(*timep, 10000000), time_offset); + + RPC_NEXT32 = ex64lo(hgfstime); + RPC_NEXT32 = ex64hi(hgfstime); + } else { + RPC_NEXT32 = 0; + RPC_NEXT32 = 0; + } +} + +/*===========================================================================* + * time_get * + *===========================================================================*/ +PUBLIC void time_get(timep) +time_t *timep; +{ +/* Get a HGFS timestamp from the RPC buffer, convert it into a UNIX timestamp, + * and store the result in the given time pointer. If the given pointer is + * NULL, however, simply skip over the timestamp in the RPC buffer. + */ + u64_t hgfstime; + u32_t time_lo, time_hi; + + if (timep != NULL) { + time_lo = RPC_NEXT32; + time_hi = RPC_NEXT32; + + hgfstime = make64(time_lo, time_hi); + + *timep = div64u(sub64(hgfstime, time_offset), 10000000); + } + else RPC_ADVANCE(sizeof(u32_t) * 2); +} diff --git a/servers/hgfs/libhgfs/type.h b/servers/hgfs/libhgfs/type.h new file mode 100644 index 000000000..a8ade070d --- /dev/null +++ b/servers/hgfs/libhgfs/type.h @@ -0,0 +1,7 @@ +/* Part of libhgfs - (c) 2009, D.C. van Moolenbroek */ + +struct channel { + u16_t id; + u32_t cookie1; + u32_t cookie2; +}; diff --git a/servers/hgfs/link.c b/servers/hgfs/link.c new file mode 100644 index 000000000..0d3e468c8 --- /dev/null +++ b/servers/hgfs/link.c @@ -0,0 +1,403 @@ +/* This file contains directory entry related file system call handlers. + * + * The entry points into this file are: + * do_create perform the CREATE file system call + * do_mkdir perform the MKDIR file system call + * do_unlink perform the UNLINK file system call + * do_rmdir perform the RMDIR file system call + * do_rename perform the RENAME file system call + * + * Created: + * April 2009 (D.C. van Moolenbroek) + */ + +#include "inc.h" + +#include + +FORWARD _PROTOTYPE( int force_remove, (char *path, int dir) ); + +/*===========================================================================* + * do_create * + *===========================================================================*/ +PUBLIC int do_create() +{ +/* Create a new file. + */ + char path[PATH_MAX], name[NAME_MAX+1]; + struct inode *parent, *ino; + struct hgfs_attr attr; + hgfs_file_t handle; + int r; + + /* We cannot create files on a read-only file system. */ + if (state.read_only) + return EROFS; + + /* Get path, name, parent inode and possibly inode for the given path. */ + if ((r = get_name(m_in.REQ_GRANT, m_in.REQ_PATH_LEN, name)) != OK) + return r; + + if (!strcmp(name, ".") || !strcmp(name, "..")) return EEXIST; + + if ((parent = find_inode(m_in.REQ_INODE_NR)) == NIL_INODE) + return EINVAL; + + if ((r = verify_dentry(parent, name, path, &ino)) != OK) + return r; + + /* Are we going to need a new inode upon success? + * Then make sure there is one available before trying anything. + */ + if (ino == NIL_INODE || ino->i_ref > 1 || HAS_CHILDREN(ino)) { + if (!have_free_inode()) { + if (ino != NIL_INODE) + put_inode(ino); + + return ENFILE; + } + } + + /* Perform the actual create call. */ + r = hgfs_open(path, O_CREAT | O_EXCL | O_RDWR, m_in.REQ_MODE, &handle); + + if (r != OK) { + /* Let's not try to be too clever with error codes here. If something + * is wrong with the directory, we'll find out later anyway. + */ + + if (ino != NIL_INODE) + put_inode(ino); + + return r; + } + + /* Get the created file's attributes. */ + attr.a_mask = HGFS_ATTR_MODE | HGFS_ATTR_SIZE; + r = hgfs_getattr(path, &attr); + + /* If this fails, or returns a directory, we have a problem. This + * scenario is in fact possible with race conditions. + * Simulate a close and return a somewhat appropriate error. + */ + if (r != OK || S_ISDIR(attr.a_mode)) { + printf("HGFS: lost file after creation!\n"); + + hgfs_close(handle); + + if (ino != NIL_INODE) { + del_dentry(ino); + + put_inode(ino); + } + + return (r == OK) ? EEXIST : r; + } + + /* We do assume that the HGFS open(O_CREAT|O_EXCL) did its job. + * If we previousy found an inode, get rid of it now. It's old. + */ + if (ino != NIL_INODE) { + del_dentry(ino); + + put_inode(ino); + } + + /* Associate the open file handle with an inode, and reply with its details. + */ + ino = get_free_inode(); + + assert(ino != NIL_INODE); /* we checked before whether we had a free one */ + + ino->i_file = handle; + ino->i_flags = I_HANDLE; + + add_dentry(parent, name, ino); + + m_out.RES_INODE_NR = INODE_NR(ino); + m_out.RES_MODE = get_mode(ino, attr.a_mode); + m_out.RES_FILE_SIZE_HI = ex64hi(attr.a_size); + m_out.RES_FILE_SIZE_LO = ex64lo(attr.a_size); + m_out.RES_UID = opt.uid; + m_out.RES_GID = opt.gid; + m_out.RES_DEV = NO_DEV; + + return OK; +} + +/*===========================================================================* + * do_mkdir * + *===========================================================================*/ +PUBLIC int do_mkdir() +{ +/* Make a new directory. + */ + char path[PATH_MAX], name[NAME_MAX+1]; + struct inode *parent, *ino; + int r; + + /* We cannot create directories on a read-only file system. */ + if (state.read_only) + return EROFS; + + /* Get the path string and possibly an inode for the given path. */ + if ((r = get_name(m_in.REQ_GRANT, m_in.REQ_PATH_LEN, name)) != OK) + return r; + + if (!strcmp(name, ".") || !strcmp(name, "..")) return EEXIST; + + if ((parent = find_inode(m_in.REQ_INODE_NR)) == NIL_INODE) + return EINVAL; + + if ((r = verify_dentry(parent, name, path, &ino)) != OK) + return r; + + /* Perform the actual mkdir call. */ + r = hgfs_mkdir(path, m_in.REQ_MODE); + + if (r != OK) { + if (ino != NIL_INODE) + put_inode(ino); + + return r; + } + + /* If we thought the new dentry already existed, it was apparently gone + * already. Delete it. + */ + if (ino != NIL_INODE) { + del_dentry(ino); + + put_inode(ino); + } + + return OK; +} + +/*===========================================================================* + * force_remove * + *===========================================================================*/ +PRIVATE int force_remove(path, dir) +char *path; /* path to file or directory */ +int dir; /* TRUE iff directory */ +{ +/* Remove a file or directory. Wrapper around hgfs_unlink and hgfs_rmdir that + * makes the target temporarily writable if the operation fails with an access + * denied error. On Windows hosts, read-only files or directories cannot be + * removed (even though they can be renamed). In general, the HGFS server + * follows the behavior of the host file system, but this case just confuses + * the hell out of the MINIX userland.. + */ + struct hgfs_attr attr; + int r, r2; + + /* First try to remove the target. */ + if (dir) + r = hgfs_rmdir(path); + else + r = hgfs_unlink(path); + + if (r != EACCES) return r; + + /* If this fails with an access error, retrieve the target's mode. */ + attr.a_mask = HGFS_ATTR_MODE; + + r2 = hgfs_getattr(path, &attr); + + if (r2 != OK || (attr.a_mode & S_IWUSR)) return r; + + /* If the target is not writable, temporarily set it to writable. */ + attr.a_mode |= S_IWUSR; + + r2 = hgfs_setattr(path, &attr); + + if (r2 != OK) return r; + + /* Then try the original operation again. */ + if (dir) + r = hgfs_rmdir(path); + else + r = hgfs_unlink(path); + + if (r == OK) return r; + + /* If the operation still fails, unset the writable bit again. */ + attr.a_mode &= ~S_IWUSR; + + hgfs_setattr(path, &attr); + + return r; +} + +/*===========================================================================* + * do_unlink * + *===========================================================================*/ +PUBLIC int do_unlink() +{ +/* Delete a file. + */ + char path[PATH_MAX], name[NAME_MAX+1]; + struct inode *parent, *ino; + int r; + + /* We cannot delete files on a read-only file system. */ + if (state.read_only) + return EROFS; + + /* Get the path string and possibly preexisting inode for the given path. */ + if ((r = get_name(m_in.REQ_GRANT, m_in.REQ_PATH_LEN, name)) != OK) + return r; + + if (!strcmp(name, ".") || !strcmp(name, "..")) return EPERM; + + if ((parent = find_inode(m_in.REQ_INODE_NR)) == NIL_INODE) + return EINVAL; + + if ((r = verify_dentry(parent, name, path, &ino)) != OK) + return r; + + /* Perform the unlink call. */ + r = force_remove(path, FALSE /*dir*/); + + if (r != OK) { + if (ino != NIL_INODE) + put_inode(ino); + + return r; + } + + /* If a dentry existed for this name, it is gone now. */ + if (ino != NIL_INODE) { + del_dentry(ino); + + put_inode(ino); + } + + return OK; +} + +/*===========================================================================* + * do_rmdir * + *===========================================================================*/ +PUBLIC int do_rmdir() +{ +/* Remove an empty directory. + */ + char path[PATH_MAX], name[NAME_MAX+1]; + struct inode *parent, *ino; + int r; + + /* We cannot remove directories on a read-only file system. */ + if (state.read_only) + return EROFS; + + /* Get the path string and possibly preexisting inode for the given path. */ + if ((r = get_name(m_in.REQ_GRANT, m_in.REQ_PATH_LEN, name)) != OK) + return r; + + if (!strcmp(name, ".")) return EINVAL; + if (!strcmp(name, "..")) return ENOTEMPTY; + + if ((parent = find_inode(m_in.REQ_INODE_NR)) == NIL_INODE) + return EINVAL; + + if ((r = verify_dentry(parent, name, path, &ino)) != OK) + return r; + + /* Perform the rmdir call. */ + r = force_remove(path, TRUE /*dir*/); + + if (r != OK) { + if (ino != NIL_INODE) + put_inode(ino); + + return r; + } + + /* If a dentry existed for this name, it is gone now. */ + if (ino != NIL_INODE) { + del_dentry(ino); + + put_inode(ino); + } + + return OK; +} + +/*===========================================================================* + * do_rename * + *===========================================================================*/ +PUBLIC int do_rename() +{ +/* Rename a file or directory. + */ + char old_path[PATH_MAX], new_path[PATH_MAX]; + char old_name[NAME_MAX+1], new_name[NAME_MAX+1]; + struct inode *old_parent, *new_parent; + struct inode *old_ino, *new_ino; + int r; + + /* We cannot do rename on a read-only file system. */ + if (state.read_only) + return EROFS; + + /* Get path strings, names, directory inodes and possibly preexisting inodes + * for the old and new paths. + */ + if ((r = get_name(m_in.REQ_REN_GRANT_OLD, m_in.REQ_REN_LEN_OLD, + old_name)) != OK) return r; + + if ((r = get_name(m_in.REQ_REN_GRANT_NEW, m_in.REQ_REN_LEN_NEW, + new_name)) != OK) return r; + + if (!strcmp(old_name, ".") || !strcmp(old_name, "..") || + !strcmp(new_name, ".") || !strcmp(new_name, "..")) return EINVAL; + + if ((old_parent = find_inode(m_in.REQ_REN_OLD_DIR)) == NIL_INODE || + (new_parent = find_inode(m_in.REQ_REN_NEW_DIR)) == NIL_INODE) + return EINVAL; + + if ((r = verify_dentry(old_parent, old_name, old_path, &old_ino)) != OK) + return r; + + if ((r = verify_dentry(new_parent, new_name, new_path, &new_ino)) != OK) { + if (old_ino != NIL_INODE) + put_inode(old_ino); + + return r; + } + + /* Perform the actual rename call. */ + r = hgfs_rename(old_path, new_path); + + /* If we failed, or if we have nothing further to do: both inodes are + * NULL, or they both refer to the same file. + */ + if (r != OK || old_ino == new_ino) { + if (old_ino != NIL_INODE) put_inode(old_ino); + + if (new_ino != NIL_INODE) put_inode(new_ino); + + return r; + } + + /* If the new dentry already existed, it has now been overwritten. + * Delete the associated inode if we had found one. + */ + if (new_ino != NIL_INODE) { + del_dentry(new_ino); + + put_inode(new_ino); + } + + /* If the old dentry existed, rename it accordingly. */ + if (old_ino != NIL_INODE) { + del_dentry(old_ino); + + add_dentry(new_parent, new_name, old_ino); + + put_inode(old_ino); + } + + return OK; +} diff --git a/servers/hgfs/lookup.c b/servers/hgfs/lookup.c new file mode 100644 index 000000000..8de9b73b0 --- /dev/null +++ b/servers/hgfs/lookup.c @@ -0,0 +1,333 @@ +/* This file provides path-to-inode lookup functionality. + * + * The entry points into this file are: + * do_lookup perform the LOOKUP file system call + * + * Created: + * April 2009 (D.C. van Moolenbroek) + */ + +#include "inc.h" + +FORWARD _PROTOTYPE( int get_mask, (vfs_ucred_t *ucred) ); +FORWARD _PROTOTYPE( int access_as_dir, (struct inode *ino, + struct hgfs_attr *attr, int uid, int mask) ); +FORWARD _PROTOTYPE( int next_name, (char **ptr, char **start, + char name[NAME_MAX+1]) ); +FORWARD _PROTOTYPE( int go_up, (char path[PATH_MAX], struct inode *ino, + struct inode **res_ino, struct hgfs_attr *attr) ); +FORWARD _PROTOTYPE( int go_down, (char path[PATH_MAX], + struct inode *ino, char *name, + struct inode **res_ino, struct hgfs_attr *attr) ); + +/*===========================================================================* + * get_mask * + *===========================================================================*/ +PRIVATE int get_mask(ucred) +vfs_ucred_t *ucred; /* credentials of the caller */ +{ + /* Given the caller's credentials, precompute a search access mask to test + * against directory modes. + */ + int i; + + if (ucred->vu_uid == opt.uid) return S_IXUSR; + + if (ucred->vu_gid == opt.gid) return S_IXGRP; + + for (i = 0; i < ucred->vu_ngroups; i++) + if (ucred->vu_sgroups[i] == opt.gid) return S_IXGRP; + + return S_IXOTH; +} + +/*===========================================================================* + * access_as_dir * + *===========================================================================*/ +PRIVATE int access_as_dir(ino, attr, uid, mask) +struct inode *ino; /* the inode to test */ +struct hgfs_attr *attr; /* attributes of the inode */ +int uid; /* UID of the caller */ +int mask; /* search access mask of the caller */ +{ +/* Check whether the given inode may be accessed as directory. + * Return OK or an appropriate error code. + */ + mode_t mode; + + assert(attr->a_mask & HGFS_ATTR_MODE); + + /* The inode must be a directory to begin with. */ + if (!IS_DIR(ino)) return ENOTDIR; + + /* The caller must have search access to the directory. Root always does. */ + if (uid == 0) return OK; + + mode = get_mode(ino, attr->a_mode); + + return (mode & mask) ? OK : EACCES; +} + +/*===========================================================================* + * next_name * + *===========================================================================*/ +PRIVATE int next_name(ptr, start, name) +char **ptr; /* cursor pointer into path (in, out) */ +char **start; /* place to store start of name */ +char name[NAME_MAX+1]; /* place to store name */ +{ +/* Get the next path component from a path. + */ + char *p; + int i; + + for (p = *ptr; *p == '/'; p++); + + *start = p; + + if (*p) { + for (i = 0; *p && *p != '/' && i <= NAME_MAX; p++, i++) + name[i] = *p; + + if (i > NAME_MAX) + return ENAMETOOLONG; + + name[i] = 0; + } else { + strcpy(name, "."); + } + + *ptr = p; + return OK; +} + +/*===========================================================================* + * go_up * + *===========================================================================*/ +PRIVATE int go_up(path, ino, res_ino, attr) +char path[PATH_MAX]; /* path to take the last part from */ +struct inode *ino; /* inode of the current directory */ +struct inode **res_ino; /* place to store resulting inode */ +struct hgfs_attr *attr; /* place to store inode attributes */ +{ +/* Given an inode, progress into the parent directory. + */ + struct inode *parent; + int r; + + pop_path(path); + + parent = ino->i_parent; + assert(parent != NIL_INODE); + + if ((r = verify_path(path, parent, attr, NULL)) != OK) + return r; + + get_inode(parent); + + *res_ino = parent; + + return r; +} + +/*===========================================================================* + * go_down * + *===========================================================================*/ +PRIVATE int go_down(path, parent, name, res_ino, attr) +char path[PATH_MAX]; /* path to add the name to */ +struct inode *parent; /* inode of the current directory */ +char *name; /* name of the directory entry */ +struct inode **res_ino; /* place to store resulting inode */ +struct hgfs_attr *attr; /* place to store inode attributes */ +{ +/* Given a directory inode and a name, progress into a directory entry. + */ + struct inode *ino; + int r, stale = 0; + + if ((r = push_path(path, name)) != OK) + return r; + + dprintf(("HGFS: go_down: name '%s', path now '%s'\n", name, path)); + + ino = lookup_dentry(parent, name); + + dprintf(("HGFS: lookup_dentry('%s') returned %p\n", name, ino)); + + if (ino != NIL_INODE) + r = verify_path(path, ino, attr, &stale); + else + r = hgfs_getattr(path, attr); + + dprintf(("HGFS: path query returned %d\n", r)); + + if (r != OK) { + if (ino != NIL_INODE) { + put_inode(ino); + + ino = NIL_INODE; + } + + if (!stale) + return r; + } + + dprintf(("HGFS: name '%s'\n", name)); + + if (ino == NIL_INODE) { + if ((ino = get_free_inode()) == NIL_INODE) + return ENFILE; + + dprintf(("HGFS: inode %p ref %d\n", ino, ino->i_ref)); + + ino->i_flags = MODE_TO_DIRFLAG(attr->a_mode); + + add_dentry(parent, name, ino); + } + + *res_ino = ino; + return OK; +} + +/*===========================================================================* + * do_lookup * + *===========================================================================*/ +PUBLIC int do_lookup() +{ +/* Resolve a path string to an inode. + */ + ino_t dir_ino_nr, root_ino_nr; + struct inode *cur_ino, *next_ino, *root_ino; + struct hgfs_attr attr; + char buf[PATH_MAX], path[PATH_MAX]; + char name[NAME_MAX+1]; + char *ptr, *last; + vfs_ucred_t ucred; + mode_t mask; + size_t len; + int r; + + dir_ino_nr = m_in.REQ_DIR_INO; + root_ino_nr = m_in.REQ_ROOT_INO; + len = m_in.REQ_PATH_LEN; + + /* Fetch the path name. */ + if (len < 1 || len > PATH_MAX) + return EINVAL; + + r = sys_safecopyfrom(m_in.m_source, m_in.REQ_GRANT, 0, + (vir_bytes) buf, len, D); + + if (r != OK) + return r; + + if (buf[len-1] != 0) { + printf("HGFS: VFS did not zero-terminate path!\n"); + + return EINVAL; + } + + /* Fetch the credentials, and generate a search access mask to test against + * directory modes. + */ + if (m_in.REQ_FLAGS & PATH_GET_UCRED) { + if (m_in.REQ_UCRED_SIZE != sizeof(ucred)) { + printf("HGFS: bad credential structure size\n"); + + return EINVAL; + } + + r = sys_safecopyfrom(m_in.m_source, m_in.REQ_GRANT2, 0, + (vir_bytes) &ucred, m_in.REQ_UCRED_SIZE, D); + + if (r != OK) + return r; + } + else { + ucred.vu_uid = m_in.REQ_UID; + ucred.vu_gid = m_in.REQ_GID; + ucred.vu_ngroups = 0; + } + + mask = get_mask(&ucred); + + /* Start the actual lookup. */ + dprintf(("HGFS: lookup: got query '%s'\n", buf)); + + if ((cur_ino = find_inode(dir_ino_nr)) == NIL_INODE) + return EINVAL; + + attr.a_mask = HGFS_ATTR_MODE | HGFS_ATTR_SIZE; + + if ((r = verify_inode(cur_ino, path, &attr)) != OK) + return r; + + get_inode(cur_ino); + + if (root_ino_nr > 0) + root_ino = find_inode(root_ino_nr); + else + root_ino = NIL_INODE; + + /* One possible optimization would be to check a path only right before the + * first ".." in a row, and at the very end (if still necessary). This would + * have consequences for inode validation, though. + */ + for (ptr = last = buf; *ptr != 0; ) { + if ((r = access_as_dir(cur_ino, &attr, ucred.vu_uid, mask)) != OK) + break; + + if ((r = next_name(&ptr, &last, name)) != OK) + break; + + dprintf(("HGFS: lookup: next name '%s'\n", name)); + + if (!strcmp(name, ".") || + (cur_ino == root_ino && !strcmp(name, ".."))) + continue; + + if (!strcmp(name, "..")) { + if (IS_ROOT(cur_ino)) + r = ELEAVEMOUNT; + else + r = go_up(path, cur_ino, &next_ino, &attr); + } else { + r = go_down(path, cur_ino, name, &next_ino, &attr); + } + + if (r != OK) + break; + + assert(next_ino != NIL_INODE); + + put_inode(cur_ino); + + cur_ino = next_ino; + } + + dprintf(("HGFS: lookup: result %d\n", r)); + + if (r != OK) { + put_inode(cur_ino); + + /* We'd need support for these here. We don't have such support. */ + assert(r != EENTERMOUNT && r != ESYMLINK); + + if (r == ELEAVEMOUNT) { + m_out.RES_OFFSET = (int)(last - buf); + m_out.RES_SYMLOOP = 0; + } + + return r; + } + + m_out.RES_INODE_NR = INODE_NR(cur_ino); + m_out.RES_MODE = get_mode(cur_ino, attr.a_mode); + m_out.RES_FILE_SIZE_HI = ex64hi(attr.a_size); + m_out.RES_FILE_SIZE_LO = ex64lo(attr.a_size); + m_out.RES_UID = opt.uid; + m_out.RES_GID = opt.gid; + m_out.RES_DEV = NO_DEV; + + return OK; +} diff --git a/servers/hgfs/main.c b/servers/hgfs/main.c new file mode 100644 index 000000000..9f0924a64 --- /dev/null +++ b/servers/hgfs/main.c @@ -0,0 +1,232 @@ +/* This file contains the main message loop of the HGFS file system server. + * + * The entry points into this file are: + * main main program function + * + * Created: + * April 2009 (D.C. van Moolenbroek) + */ + +#include "inc.h" + +#include "optset.h" + +#include +#include + +FORWARD _PROTOTYPE( int init, (int type, sef_init_info_t *info) ); +FORWARD _PROTOTYPE( void sef_local_startup, (void) ); +FORWARD _PROTOTYPE( void cleanup, (void) ); +FORWARD _PROTOTYPE( int get_work, (endpoint_t *who_e) ); +FORWARD _PROTOTYPE( void send_reply, (int err) ); +FORWARD _PROTOTYPE( int proc_event, (void) ); + +PRIVATE struct optset optset_table[] = { + { "prefix", OPT_STRING, opt.prefix, sizeof(opt.prefix) }, + { "uid", OPT_INT, &opt.uid, 10 }, + { "gid", OPT_INT, &opt.gid, 10 }, + { "fmask", OPT_INT, &opt.file_mask, 8 }, + { "dmask", OPT_INT, &opt.dir_mask, 8 }, + { "icase", OPT_BOOL, &opt.case_insens, TRUE }, + { "noicase", OPT_BOOL, &opt.case_insens, FALSE }, + { NULL } +}; + +EXTERN int env_argc; +EXTERN char **env_argv; + +/*===========================================================================* + * init * + *===========================================================================*/ +PRIVATE int init(type, info) +int type; +sef_init_info_t *info; +{ +/* Initialize this file server. Called at startup time. + */ + message m; + int i, r; + + /* Defaults */ + opt.prefix[0] = 0; + opt.uid = 0; + opt.gid = 0; + opt.file_mask = 0755; + opt.dir_mask = 0755; + opt.case_insens = FALSE; + + /* If we have been given an options string, parse options from there. */ + for (i = 1; i < env_argc - 1; i++) + if (!strcmp(env_argv[i], "-o")) + optset_parse(optset_table, env_argv[++i]); + + /* Make sure that the given path prefix doesn't end with a slash. */ + for (i = strlen(opt.prefix); i > 0 && opt.prefix[i - 1] == '/'; i--); + opt.prefix[i] = 0; + + /* Initialize the HGFS library. If this fails, exit immediately. */ + r = hgfs_init(); + if (r != OK && r != EAGAIN) { + printf("HGFS: unable to initialize HGFS library (%d)\n", r); + + return r; + } + + state.mounted = FALSE; + + /* Announce our presence to VFS. */ + m.m_type = FS_READY; + + if ((r = send(FS_PROC_NR, &m)) != OK) { + printf("HGFS: unable to login to VFS (%d)\n", r); + + return r; + } + + return OK; +} + +/*===========================================================================* + * sef_local_startup * + *===========================================================================*/ +PRIVATE void sef_local_startup() +{ +/* Specify initialization routines and start the SEF framework. + */ + + sef_setcb_init_fresh(init); + sef_setcb_init_restart(init); + + /* No live update support yet. */ + + sef_startup(); +} + +/*===========================================================================* + * cleanup * + *===========================================================================*/ +PRIVATE void cleanup() +{ +/* Clean up any resources in use by this file server. Called at shutdown time. + */ + + /* Pass on the cleanup request to the HGFS library. */ + hgfs_cleanup(); +} + +/*===========================================================================* + * main * + *===========================================================================*/ +PUBLIC int main(argc, argv) +int argc; +char *argv[]; +{ +/* The main function of this file server. After initializing, loop forever + * receiving one request from VFS at a time, processing it, and sending a + * reply back to VFS. + */ + endpoint_t who_e; + int call_nr, err; + + env_setargs(argc, argv); + sef_local_startup(); + + for (;;) { + call_nr = get_work(&who_e); + + if (who_e != FS_PROC_NR) { + /* Is this PM telling us to shut down? */ + if (who_e == PM_PROC_NR && is_notify(call_nr)) + if (proc_event()) break; + + continue; + } + + if (state.mounted || call_nr == REQ_READSUPER) { + call_nr -= VFS_BASE; + + dprintf(("HGFS: call %d\n", call_nr)); + + if (call_nr >= 0 && call_nr < NREQS) { + err = (*call_vec[call_nr])(); + } else { + err = ENOSYS; + } + + dprintf(("HGFS: call %d result %d\n", call_nr, err)); + } + else err = EINVAL; + + send_reply(err); + } + + cleanup(); + + return 0; +} + +/*===========================================================================* + * get_work * + *===========================================================================*/ +PRIVATE int get_work(who_e) +endpoint_t *who_e; +{ +/* Receive a request message from VFS. Return the request call number. + */ + int r; + + if ((r = sef_receive(ANY, &m_in)) != OK) + panic("HGFS", "receive failed", r); + + *who_e = m_in.m_source; + + return m_in.m_type; +} + +/*===========================================================================* + * send_reply * + *===========================================================================*/ +PRIVATE void send_reply(err) +int err; /* resulting error code */ +{ +/* Send a reply message to the requesting party, with the given error code. + */ + int r; + + m_out.m_type = err; + + if ((r = send(m_in.m_source, &m_out)) != OK) + printf("HGFS: send failed (%d)\n", r); +} + +/*===========================================================================* + * proc_event * + *===========================================================================*/ +PRIVATE int proc_event() +{ +/* We got a notification from PM; see what it's about. Return TRUE if this + * server has been told to shut down. + */ + sigset_t set; + int r; + + if ((r = getsigset(&set)) != OK) { + printf("HGFS: unable to get pending signals from PM (%d)\n", r); + + return FALSE; + } + + if (sigismember(&set, SIGTERM)) { + if (state.mounted) { + dprintf(("HGFS: got SIGTERM, still mounted\n")); + + return FALSE; + } + + dprintf(("HGFS: got SIGTERM, shutting down\n")); + + return TRUE; + } + + return FALSE; +} diff --git a/servers/hgfs/misc.c b/servers/hgfs/misc.c new file mode 100644 index 000000000..b7c4c60c1 --- /dev/null +++ b/servers/hgfs/misc.c @@ -0,0 +1,27 @@ +/* This file contains miscellaneous file system call handlers. + * + * The entry points into this file are: + * do_fstatfs perform the FSTATFS file system call + * + * Created: + * April 2009 (D.C. van Moolenbroek) + */ + +#include "inc.h" + +#include + +/*===========================================================================* + * do_fstatfs * + *===========================================================================*/ +PUBLIC int do_fstatfs() +{ +/* Retrieve file system statistics. + */ + struct statfs statfs; + + statfs.f_bsize = BLOCK_SIZE; /* arbitrary block size constant */ + + return sys_safecopyto(m_in.m_source, m_in.REQ_GRANT, 0, + (vir_bytes) &statfs, sizeof(statfs), D); +} diff --git a/servers/hgfs/mount.c b/servers/hgfs/mount.c new file mode 100644 index 000000000..9cc3a089e --- /dev/null +++ b/servers/hgfs/mount.c @@ -0,0 +1,94 @@ +/* This file contains mount and unmount functionality. + * + * The entry points into this file are: + * do_readsuper perform the READSUPER file system call + * do_unmount perform the UNMOUNT file system call + * + * Created: + * April 2009 (D.C. van Moolenbroek) + */ + +#include "inc.h" + +/*===========================================================================* + * do_readsuper * + *===========================================================================*/ +PUBLIC int do_readsuper() +{ +/* Mount the file system. + */ + char path[PATH_MAX]; + struct inode *ino; + struct hgfs_attr attr; + int r; + + dprintf(("HGFS: readsuper (dev %x, flags %x)\n", + m_in.REQ_DEV, m_in.REQ_FLAGS)); + + if (m_in.REQ_FLAGS & REQ_ISROOT) { + printf("HGFS: attempt to mount as root device\n"); + + return EINVAL; + } + + state.read_only = !!(m_in.REQ_FLAGS & REQ_RDONLY); + state.dev = m_in.REQ_DEV; + + init_dentry(); + ino = init_inode(); + + attr.a_mask = HGFS_ATTR_MODE | HGFS_ATTR_SIZE; + + /* We cannot continue if we fail to get the properties of the root inode at + * all, because we cannot guess the details of the root node to return to + * VFS. Print a (hopefully) helpful error message, and abort the mount. + */ + if ((r = verify_inode(ino, path, &attr)) != OK) { + if (r == EAGAIN) + printf("HGFS: shared folders disabled\n"); + else if (opt.prefix[0] && (r == ENOENT || r == EACCES)) + printf("HGFS: unable to access the given prefix directory\n"); + else + printf("HGFS: unable to access shared folders\n"); + + return r; + } + + m_out.RES_INODE_NR = INODE_NR(ino); + m_out.RES_MODE = get_mode(ino, attr.a_mode); + m_out.RES_FILE_SIZE_HI = ex64hi(attr.a_size); + m_out.RES_FILE_SIZE_LO = ex64lo(attr.a_size); + m_out.RES_UID = opt.uid; + m_out.RES_GID = opt.gid; + m_out.RES_DEV = NO_DEV; + + state.mounted = TRUE; + + return OK; +} + +/*===========================================================================* + * do_unmount * + *===========================================================================*/ +PUBLIC int do_unmount() +{ +/* Unmount the file system. + */ + struct inode *ino; + + dprintf(("HGFS: do_unmount\n")); + + /* Decrease the reference count of the root inode. */ + if ((ino = find_inode(ROOT_INODE_NR)) == NIL_INODE) + return EINVAL; + + put_inode(ino); + + /* There should not be any referenced inodes anymore now. */ + if (have_used_inode()) + printf("HGFS: in-use inodes left at unmount time!\n"); + + state.mounted = FALSE; + + return OK; +} diff --git a/servers/hgfs/name.c b/servers/hgfs/name.c new file mode 100644 index 000000000..39aa54063 --- /dev/null +++ b/servers/hgfs/name.c @@ -0,0 +1,56 @@ +/* This file contains path component name utility functions. + * + * The entry points into this file are: + * normalize_name normalize a path component name for hashing purposes + * compare_name check whether two path component names are equivalent + * + * Created: + * April 2009 (D.C. van Moolenbroek) + */ + +#include "inc.h" + +#include + +/*===========================================================================* + * normalize_name * + *===========================================================================*/ +PUBLIC void normalize_name(dst, src) +char dst[NAME_MAX+1]; +char *src; +{ +/* Normalize the given path component name, storing the result in the given + * buffer. + */ + size_t i, size; + + size = strlen(src) + 1; + + assert(size <= NAME_MAX+1); + + if (opt.case_insens) { + for (i = 0; i < size; i++) + *dst++ = tolower(*src++); + } + else memcpy(dst, src, size); +} + +/*===========================================================================* + * compare_name * + *===========================================================================*/ +PUBLIC int compare_name(name1, name2) +char *name1; +char *name2; +{ +/* Return TRUE if the given path component names are equivalent, FALSE + * otherwise. + */ + int r; + + if (opt.case_insens) + r = strcasecmp(name1, name2); + else + r = strcmp(name1, name2); + + return r ? FALSE : TRUE; +} diff --git a/servers/hgfs/optset.c b/servers/hgfs/optset.c new file mode 100644 index 000000000..a338a4717 --- /dev/null +++ b/servers/hgfs/optset.c @@ -0,0 +1,128 @@ +/* This file provides functionality to parse strings of comma-separated + * options, each being either a single key name or a key=value pair, where the + * value may be enclosed in quotes. A table of optset entries is provided to + * determine which options are recognized, how to parse their values, and where + * to store those. Unrecognized options are silently ignored; improperly + * formatted options are silently set to reasonably acceptable values. + * + * The entry points into this file are: + * optset_parse parse the given options string using the given table + * + * Created: + * May 2009 (D.C. van Moolenbroek) + */ + +#define _MINIX 1 +#include +#include +#include +#include + +#include "optset.h" + +FORWARD _PROTOTYPE( void optset_parse_entry, (struct optset *entry, + char *ptr, int len) ); + +/*===========================================================================* + * optset_parse_entry * + *===========================================================================*/ +PRIVATE void optset_parse_entry(entry, ptr, len) +struct optset *entry; +char *ptr; +int len; +{ +/* Parse and store the value of a single option. + */ + char *dst; + int val; + + switch (entry->os_type) { + case OPT_BOOL: + *((int *) entry->os_ptr) = entry->os_val; + + break; + + case OPT_STRING: + if (len >= entry->os_val) + len = entry->os_val - 1; + + dst = (char *) entry->os_ptr; + + if (len > 0) + memcpy(dst, ptr, len); + dst[len] = 0; + + break; + + case OPT_INT: + if (len > 0) + val = strtol(ptr, NULL, entry->os_val); + else + val = 0; + + *((int *) entry->os_ptr) = val; + + break; + } +} + +/*===========================================================================* + * optset_parse * + *===========================================================================*/ +PUBLIC void optset_parse(table, string) +struct optset *table; +char *string; +{ +/* Parse a string of options, using the provided table of optset entries. + */ + char *p, *kptr, *vptr; + int i, klen, vlen; + + for (p = string; *p; ) { + /* Get the key name for the field. */ + for (kptr = p, klen = 0; *p && *p != '=' && *p != ','; p++, klen++); + + if (*p == '=') { + /* The field has an associated value. */ + vptr = ++p; + + /* If the first character after the '=' is a quote character, + * find a matching quote character followed by either a comma + * or the terminating null character, and use the string in + * between. Otherwise, use the string up to the next comma or + * the terminating null character. + */ + if (*p == '\'' || *p == '"') { + p++; + + for (vlen = 0; *p && (*p != *vptr || + (p[1] && p[1] != ',')); p++, vlen++); + + if (*p) p++; + vptr++; + } + else + for (vlen = 0; *p && *p != ','; p++, vlen++); + } + else { + vptr = NULL; + vlen = 0; + } + + if (*p == ',') p++; + + /* Find a matching entry for this key in the given table. If found, + * call optset_parse_entry() on it. Silently ignore the option + * otherwise. + */ + for (i = 0; table[i].os_name != NULL; i++) { + if (strlen(table[i].os_name) == klen && + !strncasecmp(table[i].os_name, kptr, klen)) { + + optset_parse_entry(&table[i], vptr, vlen); + + break; + } + } + } +} diff --git a/servers/hgfs/optset.h b/servers/hgfs/optset.h new file mode 100644 index 000000000..87ea4ce9f --- /dev/null +++ b/servers/hgfs/optset.h @@ -0,0 +1,30 @@ +#ifndef _OPTSET_H +#define _OPTSET_H + +enum { + OPT_BOOL, + OPT_STRING, + OPT_INT +}; + +/* An entry for the parser of an options set. The 'os_name' field must point + * to a string, which is treated case-insensitively; the last entry of a table + * must have NULL name. The 'os_type' field must be set to one of the OPT_ + * values defined above. The 'os_ptr' field must point to the field that is to + * receive the value of a recognized option. For OPT_STRING, it must point to a + * string of a size set in 'os_val'; the resulting string may be truncated, but + * will always be null-terminated. For OPT_BOOL, it must point to an int which + * will be set to the value in 'os_val' if the option is present. For OPT_INT, + * it must point to an int which will be set to the provided option value; + * 'os_val' is then a base passed to strtol(). + */ +struct optset { + char *os_name; + int os_type; + void *os_ptr; + int os_val; +}; + +_PROTOTYPE( void optset_parse, (struct optset *table, char *string) ); + +#endif /* _OPTSET_H */ diff --git a/servers/hgfs/path.c b/servers/hgfs/path.c new file mode 100644 index 000000000..654b651a1 --- /dev/null +++ b/servers/hgfs/path.c @@ -0,0 +1,112 @@ +/* This file contains routines for creating and manipulating path strings. + * + * The entry points into this file are: + * make_path construct a path string for an inode + * push_path add a path component to the end of a path string + * pop_path remove the last path component from a path string + * + * Created: + * April 2009 (D.C. van Moolenbroek) + */ + +#include "inc.h" + +/*===========================================================================* + * make_path * + *===========================================================================*/ +PUBLIC int make_path(path, ino) +char path[PATH_MAX]; +struct inode *ino; +{ +/* Given an inode, construct the path identifying that inode. + */ + char buf[PATH_MAX], *p, *prefix; + size_t len, plen, total; + + p = &buf[sizeof(buf) - 1]; + p[0] = 0; + + dprintf(("HGFS: make_path: constructing path for inode %d\n", ino->i_num)); + + /* Get the length of the prefix, skipping any leading slashes. */ + for (prefix = opt.prefix; prefix[0] == '/'; prefix++); + plen = strlen(prefix); + + /* Construct the path right-to-left in a temporary buffer first. */ + for (total = plen; ino != NIL_INODE && !IS_ROOT(ino); ino = ino->i_parent) { + len = strlen(ino->i_name); + + total += len + 1; + p -= len + 1; + + if (total >= sizeof(buf)) + return ENAMETOOLONG; + + p[0] = '/'; + memcpy(p + 1, ino->i_name, len); + } + + /* If any of the intermediate inodes has no parent, the final inode is no + * longer addressable by name. + */ + if (ino == NIL_INODE) + return ENOENT; + + /* Put the result in the actual buffer. We need the leading slash in the + * temporary buffer only when the prefix is not empty. + */ + if (!prefix[0] && p[0] == '/') p++; + + strcpy(path, prefix); + strcpy(&path[plen], p); + + dprintf(("HGFS: make_path: resulting path is '%s'\n", path)); + + return OK; +} + +/*===========================================================================* + * push_path * + *===========================================================================*/ +PUBLIC int push_path(path, name) +char path[PATH_MAX]; +char *name; +{ +/* Add a component to the end of a path. + */ + size_t len, add; + + len = strlen(path); + add = strlen(name); + if (len > 0) add++; + + if (len + add >= PATH_MAX) + return ENAMETOOLONG; + + if (len > 0) path[len++] = '/'; + strcpy(&path[len], name); + + return OK; +} + +/*===========================================================================* + * pop_path * + *===========================================================================*/ +PUBLIC void pop_path(path) +char path[PATH_MAX]; +{ +/* Remove the last component from a path. + */ + char *p; + + p = strrchr(path, '/'); + + if (p == NULL) { + p = path; + + /* Can't pop the root component */ + assert(p[0] != 0); + } + + p[0] = 0; +} diff --git a/servers/hgfs/proto.h b/servers/hgfs/proto.h new file mode 100644 index 000000000..93575c99b --- /dev/null +++ b/servers/hgfs/proto.h @@ -0,0 +1,82 @@ + +/* dentry.c */ +_PROTOTYPE( void init_dentry, (void) ); +_PROTOTYPE( struct inode *lookup_dentry, (struct inode *parent, + char *name) ); +_PROTOTYPE( void add_dentry, (struct inode *parent, char *name, + struct inode *ino) ); +_PROTOTYPE( void del_dentry, (struct inode *ino) ); + +/* handle.c */ +_PROTOTYPE( int get_handle, (struct inode *ino) ); +_PROTOTYPE( void put_handle, (struct inode *ino) ); + +/* inode.c */ +_PROTOTYPE( struct inode *init_inode, (void) ); +_PROTOTYPE( struct inode *find_inode, (ino_t ino_nr) ); +_PROTOTYPE( void get_inode, (struct inode *ino) ); +_PROTOTYPE( void put_inode, (struct inode *ino) ); +_PROTOTYPE( void link_inode, (struct inode *parent, struct inode *ino) ); +_PROTOTYPE( void unlink_inode, (struct inode *ino) ); +_PROTOTYPE( struct inode *get_free_inode, (void) ); +_PROTOTYPE( int have_free_inode, (void) ); +_PROTOTYPE( int have_used_inode, (void) ); +_PROTOTYPE( int do_putnode, (void) ); + +/* link.c */ +_PROTOTYPE( int do_create, (void) ); +_PROTOTYPE( int do_mkdir, (void) ); +_PROTOTYPE( int do_unlink, (void) ); +_PROTOTYPE( int do_rmdir, (void) ); +_PROTOTYPE( int do_rename, (void) ); + +/* lookup.c */ +_PROTOTYPE( int do_lookup, (void) ); + +/* main.c */ +_PROTOTYPE( int main, (int argc, char *argv[]) ); + +/* misc.c */ +_PROTOTYPE( int do_fstatfs, (void) ); + +/* mount.c */ +_PROTOTYPE( int do_readsuper, (void) ); +_PROTOTYPE( int do_unmount, (void) ); + +/* name.c */ +_PROTOTYPE( void normalize_name, (char dst[NAME_MAX+1], char *src) ); +_PROTOTYPE( int compare_name, (char *name1, char *name2) ); + +/* path.c */ +_PROTOTYPE( int make_path, (char path[PATH_MAX], struct inode *ino) ); +_PROTOTYPE( int push_path, (char path[PATH_MAX], char *name) ); +_PROTOTYPE( void pop_path, (char path[PATH_MAX]) ); + +/* read.c */ +_PROTOTYPE( int do_read, (void) ); +_PROTOTYPE( int do_getdents, (void) ); + +/* stat.c */ +_PROTOTYPE( mode_t get_mode, (struct inode *ino, int mode) ); +_PROTOTYPE( int do_stat, (void) ); +_PROTOTYPE( int do_chmod, (void) ); +_PROTOTYPE( int do_utime, (void) ); + +/* util.c */ +_PROTOTYPE( int get_name, (cp_grant_id_t grant, size_t len, + char name[NAME_MAX+1]) ); +_PROTOTYPE( int do_noop, (void) ); +_PROTOTYPE( int no_sys, (void) ); + +/* verify.c */ +_PROTOTYPE( int verify_path, (char *path, struct inode *ino, + struct hgfs_attr *attr, int *stale) ); +_PROTOTYPE( int verify_inode, (struct inode *ino, char path[PATH_MAX], + struct hgfs_attr *attr) ); +_PROTOTYPE( int verify_dentry, (struct inode *parent, + char name[NAME_MAX+1], char path[PATH_MAX], + struct inode **res_ino) ); + +/* write.c */ +_PROTOTYPE( int do_write, (void) ); +_PROTOTYPE( int do_ftrunc, (void) ); diff --git a/servers/hgfs/read.c b/servers/hgfs/read.c new file mode 100644 index 000000000..5a8020920 --- /dev/null +++ b/servers/hgfs/read.c @@ -0,0 +1,239 @@ +/* This file contains file and directory reading file system call handlers. + * + * The entry points into this file are: + * do_read perform the READ file system call + * do_getdents perform the GETDENTS file system call + * + * Created: + * April 2009 (D.C. van Moolenbroek) + */ + +#include "inc.h" + +#include + +#define DWORD_ALIGN(len) (((len) + sizeof(long) - 1) & ~(sizeof(long) - 1)) + +/*===========================================================================* + * do_read * + *===========================================================================*/ +PUBLIC int do_read() +{ +/* Read data from a file. + */ + struct inode *ino; + u64_t pos; + size_t count, size; + vir_bytes off; + char *ptr; + int r, chunk; + + if ((ino = find_inode(m_in.REQ_INODE_NR)) == NIL_INODE) + return EINVAL; + + if (IS_DIR(ino)) return EISDIR; + + if ((r = get_handle(ino)) != OK) + return r; + + pos = make64(m_in.REQ_SEEK_POS_LO, m_in.REQ_SEEK_POS_HI); + count = m_in.REQ_NBYTES; + + assert(count > 0); + + /* Use the buffer from libhgfs to eliminate extra copying. */ + size = hgfs_readbuf(&ptr); + off = 0; + + while (count > 0) { + chunk = MIN(count, size); + + if ((r = hgfs_read(ino->i_file, NULL, chunk, pos)) <= 0) + break; + + chunk = r; + + r = sys_safecopyto(m_in.m_source, m_in.REQ_GRANT, off, + (vir_bytes) ptr, chunk, D); + + if (r != OK) + break; + + count -= chunk; + off += chunk; + pos = add64u(pos, chunk); + } + + if (r < 0) + return r; + + m_out.RES_SEEK_POS_HI = ex64hi(pos); + m_out.RES_SEEK_POS_LO = ex64lo(pos); + m_out.RES_NBYTES = off; + + return OK; +} + +/*===========================================================================* + * do_getdents * + *===========================================================================*/ +PUBLIC int do_getdents() +{ +/* Retrieve directory entries. + */ + char name[NAME_MAX+1]; + struct inode *ino, *child; + struct dirent *dent; + struct hgfs_attr attr; + size_t len, off, user_off, user_left; + off_t pos; + int r; + /* must be at least sizeof(struct dirent) + NAME_MAX */ + static char buf[BLOCK_SIZE]; + + attr.a_mask = HGFS_ATTR_MODE; + + if ((ino = find_inode(m_in.REQ_INODE_NR)) == NIL_INODE) + return EINVAL; + + if (m_in.REQ_SEEK_POS_HI != 0) return EINVAL; + + if (!IS_DIR(ino)) return ENOTDIR; + + /* We are going to need at least one free inode to store children in. */ + if (!have_free_inode()) return ENFILE; + + /* If we don't have a directory handle yet, get one now. */ + if ((r = get_handle(ino)) != OK) + return r; + + off = 0; + user_off = 0; + user_left = m_in.REQ_MEM_SIZE; + + /* We use the seek position as file index number. The first position is for + * the "." entry, the second position is for the ".." entry, and the next + * position numbers each represent a file in the directory. + */ + for (pos = m_in.REQ_SEEK_POS_LO; ; pos++) { + /* Determine which inode and name to use for this entry. + * We have no idea whether the HGFS host will give us "." and/or "..", + * so generate our own and skip those from the host. + */ + if (pos == 0) { + /* Entry for ".". */ + child = ino; + + strcpy(name, "."); + + get_inode(child); + } + else if (pos == 1) { + /* Entry for "..", but only when there is a parent. */ + if (ino->i_parent == NIL_INODE) + continue; + + child = ino->i_parent; + + strcpy(name, ".."); + + get_inode(child); + } + else { + /* Any other entry, not being "." or "..". */ + r = hgfs_readdir(ino->i_dir, pos - 2, name, sizeof(name), + &attr); + + if (r != OK || !name[0]) { + /* No more entries? Then close the handle and stop. */ + /* VMware Player 3 returns an empty name, instead of + * EINVAL, when reading from an EOF position right + * after opening the directory handle. Seems to be a + * newly introduced bug.. + */ + if (r == EINVAL || !name[0]) { + put_handle(ino); + + break; + } + + /* FIXME: what if the error is ENAMETOOLONG? */ + return r; + } + + if (!strcmp(name, ".") || !strcmp(name, "..")) + continue; + + if ((child = lookup_dentry(ino, name)) == NIL_INODE) { + child = get_free_inode(); + + /* We were promised a free inode! */ + assert(child != NIL_INODE); + + child->i_flags = MODE_TO_DIRFLAG(attr.a_mode); + + add_dentry(ino, name, child); + } + } + + len = DWORD_ALIGN(sizeof(struct dirent) + strlen(name)); + + /* Is the user buffer too small to store another record? + * Note that we will be rerequesting the same dentry upon a subsequent + * getdents call this way, but we really need the name length for this. + */ + if (user_off + off + len > user_left) { + put_inode(child); + + /* Is the user buffer too small for even a single record? */ + if (user_off == 0 && off == 0) + return EINVAL; + + break; + } + + /* If our own buffer cannot contain the new record, copy out first. */ + if (off + len > sizeof(buf)) { + r = sys_safecopyto(m_in.m_source, m_in.REQ_GRANT, + user_off, (vir_bytes) buf, off, D); + + if (r != OK) { + put_inode(child); + + return r; + } + + user_off += off; + user_left -= off; + off = 0; + } + + /* Fill in the actual directory entry. */ + dent = (struct dirent *) &buf[off]; + dent->d_ino = INODE_NR(child); + dent->d_off = pos; + dent->d_reclen = len; + strcpy(dent->d_name, name); + + off += len; + + put_inode(child); + } + + /* If there is anything left in our own buffer, copy that out now. */ + if (off > 0) { + r = sys_safecopyto(m_in.m_source, m_in.REQ_GRANT, user_off, + (vir_bytes) buf, off, D); + + if (r != OK) + return r; + + user_off += off; + } + + m_out.RES_SEEK_POS_HI = 0; + m_out.RES_SEEK_POS_LO = pos; + m_out.RES_NBYTES = user_off; + + return OK; +} diff --git a/servers/hgfs/stat.c b/servers/hgfs/stat.c new file mode 100644 index 000000000..92f0910eb --- /dev/null +++ b/servers/hgfs/stat.c @@ -0,0 +1,154 @@ +/* This file contains file metadata retrieval and manipulation routines. + * + * The entry points into this file are: + * get_mode return a file's mode + * do_stat perform the STAT file system call + * do_chmod perform the CHMOD file system call + * do_utime perform the UTIME file system call + * + * Created: + * April 2009 (D.C. van Moolenbroek) + */ + +#include "inc.h" + +/*===========================================================================* + * get_mode * + *===========================================================================*/ +PUBLIC mode_t get_mode(ino, mode) +struct inode *ino; +int mode; +{ +/* Return the mode for an inode, given the inode and the HGFS retrieved mode. + */ + + mode &= S_IRWXU; + mode = mode | (mode >> 3) | (mode >> 6); + + if (IS_DIR(ino)) + mode = S_IFDIR | (mode & opt.dir_mask); + else + mode = S_IFREG | (mode & opt.file_mask); + + if (state.read_only) + mode &= ~0222; + + return mode; +} + +/*===========================================================================* + * do_stat * + *===========================================================================*/ +PUBLIC int do_stat() +{ +/* Retrieve inode statistics. + */ + struct inode *ino; + struct hgfs_attr attr; + struct stat stat; + char path[PATH_MAX]; + ino_t ino_nr; + int r; + + ino_nr = m_in.REQ_INODE_NR; + + /* Don't increase the inode refcount: it's already open anyway */ + if ((ino = find_inode(ino_nr)) == NIL_INODE) + return EINVAL; + + attr.a_mask = HGFS_ATTR_MODE | HGFS_ATTR_SIZE | HGFS_ATTR_ATIME | + HGFS_ATTR_MTIME | HGFS_ATTR_CTIME; + + if ((r = verify_inode(ino, path, &attr)) != OK) + return r; + + stat.st_dev = state.dev; + stat.st_ino = ino_nr; + stat.st_mode = get_mode(ino, attr.a_mode); + stat.st_uid = opt.uid; + stat.st_gid = opt.gid; + stat.st_rdev = NO_DEV; + stat.st_size = ex64hi(attr.a_size) ? ULONG_MAX : ex64lo(attr.a_size); + stat.st_atime = attr.a_atime; + stat.st_mtime = attr.a_mtime; + stat.st_ctime = attr.a_ctime; + + /* We could make this more accurate by iterating over directory inodes' + * children, counting how many of those are directories as well. + * It's just not worth it. + */ + stat.st_nlink = 0; + if (ino->i_parent != NIL_INODE) stat.st_nlink++; + if (IS_DIR(ino)) { + stat.st_nlink++; + if (HAS_CHILDREN(ino)) stat.st_nlink++; + } + + return sys_safecopyto(m_in.m_source, m_in.REQ_GRANT, 0, + (vir_bytes) &stat, sizeof(stat), D); +} + +/*===========================================================================* + * do_chmod * + *===========================================================================*/ +PUBLIC int do_chmod() +{ +/* Change file mode. + */ + struct inode *ino; + char path[PATH_MAX]; + struct hgfs_attr attr; + int r; + + if (state.read_only) + return EROFS; + + if ((ino = find_inode(m_in.REQ_INODE_NR)) == NIL_INODE) + return EINVAL; + + if ((r = verify_inode(ino, path, NULL)) != OK) + return r; + + /* Set the new file mode. */ + attr.a_mask = HGFS_ATTR_MODE; + attr.a_mode = m_in.REQ_MODE; /* no need to convert in this direction */ + + if ((r = hgfs_setattr(path, &attr)) != OK) + return r; + + /* We have no idea what really happened. Query for the mode again. */ + if ((r = verify_path(path, ino, &attr, NULL)) != OK) + return r; + + m_out.RES_MODE = get_mode(ino, attr.a_mode); + + return OK; +} + +/*===========================================================================* + * do_utime * + *===========================================================================*/ +PUBLIC int do_utime() +{ +/* Set file times. + */ + struct inode *ino; + char path[PATH_MAX]; + struct hgfs_attr attr; + int r; + + if (state.read_only) + return EROFS; + + if ((ino = find_inode(m_in.REQ_INODE_NR)) == NIL_INODE) + return EINVAL; + + if ((r = verify_inode(ino, path, NULL)) != OK) + return r; + + attr.a_mask = HGFS_ATTR_ATIME | HGFS_ATTR_MTIME; + attr.a_atime = m_in.REQ_ACTIME; + attr.a_mtime = m_in.REQ_MODTIME; + + return hgfs_setattr(path, &attr); +} diff --git a/servers/hgfs/table.c b/servers/hgfs/table.c new file mode 100644 index 000000000..17f0c3fca --- /dev/null +++ b/servers/hgfs/table.c @@ -0,0 +1,46 @@ +/* This file contains the file system call table. + * + * Created: + * April 2009 (D.C. van Moolenbroek) + */ + +#define _TABLE +#include "inc.h" + +PUBLIC _PROTOTYPE( int (*call_vec[]), (void) ) = { + no_sys, /* 0 */ + no_sys, /* 1 getnode */ + do_putnode, /* 2 putnode */ + no_sys, /* 3 slink */ + do_ftrunc, /* 4 ftrunc */ + no_sys, /* 5 chown */ + do_chmod, /* 6 chmod */ + do_noop, /* 7 inhibread */ + do_stat, /* 8 stat */ + do_utime, /* 9 utime */ + do_fstatfs, /* 10 fstatfs */ + no_sys, /* 11 bread */ + no_sys, /* 12 bwrite */ + do_unlink, /* 13 unlink */ + do_rmdir, /* 14 rmdir */ + do_unmount, /* 15 unmount */ + do_noop, /* 16 sync */ + do_noop, /* 17 new_driver */ + do_noop, /* 18 flush */ + do_read, /* 19 read */ + do_write, /* 20 write */ + no_sys, /* 21 mknod */ + do_mkdir, /* 22 mkdir */ + do_create, /* 23 create */ + no_sys, /* 24 link */ + do_rename, /* 25 rename */ + do_lookup, /* 26 lookup */ + no_sys, /* 27 mountpoint */ + do_readsuper, /* 28 readsuper */ + no_sys, /* 29 newnode */ + no_sys, /* 30 rdlink */ + do_getdents, /* 31 getdents */ +}; + +/* This should not fail with "array size is negative": */ +extern int dummy[sizeof(call_vec) == NREQS * sizeof(call_vec[0]) ? 1 : -1]; diff --git a/servers/hgfs/type.h b/servers/hgfs/type.h new file mode 100644 index 000000000..590ec1f55 --- /dev/null +++ b/servers/hgfs/type.h @@ -0,0 +1,19 @@ + +/* Structure with global file system state. */ +struct state { + int mounted; /* is the file system mounted? */ + int read_only; /* is the file system mounted read-only? note, + * has no relation to the shared folder mode */ + dev_t dev; /* device the file system is mounted on */ +}; + +/* Structure with options affecting global behavior. */ +struct opt { + char prefix[PATH_MAX]; /* prefix for all paths used */ + uid_t uid; /* UID that owns all files */ + gid_t gid; /* GID that owns all files */ + unsigned int file_mask; /* AND-mask to apply to file permissions */ + unsigned int dir_mask; /* AND-mask to apply to directory perm's */ + int case_insens; /* case insensitivity flag; has no relation + * to the hosts's shared folder naming */ +}; diff --git a/servers/hgfs/util.c b/servers/hgfs/util.c new file mode 100644 index 000000000..af463c367 --- /dev/null +++ b/servers/hgfs/util.c @@ -0,0 +1,63 @@ +/* This file contains various utility functions. + * + * The entry points into this file are: + * get_name retrieve a path component string from VFS + * do_noop handle file system calls that do nothing and succeed + * no_sys handle file system calls that are not implemented + * + * Created: + * April 2009 (D.C. van Moolenbroek) + */ + +#include "inc.h" + +/*===========================================================================* + * get_name * + *===========================================================================*/ +PUBLIC int get_name(grant, len, name) +cp_grant_id_t grant; /* memory grant for the path component */ +size_t len; /* length of the name, including '\0' */ +char name[NAME_MAX+1]; /* buffer in which store the result */ +{ +/* Retrieve a path component from the caller, using a given grant. + */ + int r; + + /* Copy in the name of the directory entry. */ + if (len <= 1) return EINVAL; + if (len > NAME_MAX+1) return ENAMETOOLONG; + + r = sys_safecopyfrom(m_in.m_source, grant, 0, (vir_bytes) name, len, D); + + if (r != OK) return r; + + if (name[len-1] != 0) { + printf("HGFS: VFS did not zero-terminate path component!\n"); + + return EINVAL; + } + + return OK; +} + +/*===========================================================================* + * do_noop * + *===========================================================================*/ +PUBLIC int do_noop() +{ +/* Generic handler for no-op system calls. + */ + + return OK; +} + +/*===========================================================================* + * no_sys * + *===========================================================================*/ +PUBLIC int no_sys() +{ +/* Generic handler for unimplemented system calls. + */ + + return ENOSYS; +} diff --git a/servers/hgfs/verify.c b/servers/hgfs/verify.c new file mode 100644 index 000000000..681bd1b6e --- /dev/null +++ b/servers/hgfs/verify.c @@ -0,0 +1,118 @@ +/* This file contains routines that verify inodes and paths against the host. + * + * The entry points into this file are: + * verify_path check whether a path,inode pair is still valid + * verify_inode construct a path for an inode and verify the inode + * verify_dentry check a directory inode and look for a directory entry + * + * Created: + * April 2009 (D.C. van Moolenbroek) + */ + +#include "inc.h" + +/*===========================================================================* + * verify_path * + *===========================================================================*/ +PUBLIC int verify_path(path, ino, attr, stale) +char path[PATH_MAX]; +struct inode *ino; +struct hgfs_attr *attr; +int *stale; +{ +/* Given a path, and the inode associated with that path, verify if the inode + * still matches the real world. Obtain the attributes of the file identified + * by the given path, and see if they match. If not, possibly mark the inode + * as deleted and return an error. Only upon success is the inode guaranteed + * to be usable. + * + * The caller must set the a_mask field of the passed attr struct. + * If 'stale' is not NULL, the value it points to must be initialized to 0, + * and will be set to 1 if the path was valid but the inode wasn't. + */ + int r; + + attr->a_mask |= HGFS_ATTR_MODE; + + r = hgfs_getattr(path, attr); + + dprintf(("HGFS: verify_path: getattr('%s') returned %d\n", path, r)); + + if (r != OK) { + /* If we are told that the path does not exist, delete the inode */ + if (r == ENOENT || r == ENOTDIR) + del_dentry(ino); + + return r; /* path isn't valid */ + } + + /* If the file type (reg, dir) isn't what we thought, delete the inode */ + if ((ino->i_flags & I_DIR) != MODE_TO_DIRFLAG(attr->a_mode)) { + del_dentry(ino); + + if (stale != NULL) *stale = 1; + return ENOENT; /* path is valid, inode wasn't */ + } + + return OK; /* path and inode are valid */ +} + +/*===========================================================================* + * verify_inode * + *===========================================================================*/ +PUBLIC int verify_inode(ino, path, attr) +struct inode *ino; /* inode to verify */ +char path[PATH_MAX]; /* buffer in which to store the path */ +struct hgfs_attr *attr; /* buffer for attributes, or NULL */ +{ +/* Given an inode, construct a path identifying the inode, and check whether + * that path is still valid for that inode (as far as we can tell). As a side + * effect, store attributes in the given attribute structure if not NULL (its + * a_mask member must then be set). + */ + struct hgfs_attr attr2; + int r; + + if ((r = make_path(path, ino)) != OK) return r; + + if (attr == NULL) { + attr2.a_mask = 0; + + attr = &attr2; + } + + return verify_path(path, ino, attr, NULL); +} + +/*===========================================================================* + * verify_dentry * + *===========================================================================*/ +PUBLIC int verify_dentry(parent, name, path, res_ino) +struct inode *parent; /* parent inode: the inode to verify */ +char name[NAME_MAX+1]; /* the given directory entry path component */ +char path[PATH_MAX]; /* buffer to store the resulting path in */ +struct inode **res_ino; /* pointer for addressed inode (or NULL) */ +{ +/* Given a directory inode and a name, construct a path identifying that + * directory entry, check whether the path to the parent is still valid, and + * check whether there is an inode pointed to by the full path. Upon success, + * res_ino will contain either the inode for the full path, with increased + * refcount, or NIL_INODE if no such inode exists. + */ + int r; + + if ((r = verify_inode(parent, path, NULL)) != OK) + return r; + + dprintf(("HGFS: verify_dentry: given path is '%s', name '%s'\n", path, + name)); + + if ((r = push_path(path, name)) != OK) + return r; + + dprintf(("HGFS: verify_dentry: path now '%s'\n", path)); + + *res_ino = lookup_dentry(parent, name); + + return OK; +} diff --git a/servers/hgfs/write.c b/servers/hgfs/write.c new file mode 100644 index 000000000..5e3088d75 --- /dev/null +++ b/servers/hgfs/write.c @@ -0,0 +1,164 @@ +/* This file contains file writing system call handlers. + * + * The entry points into this file are: + * do_write perform the WRITE file system call + * do_ftrunc perform the FTRUNC file system call + * + * Created: + * April 2009 (D.C. van Moolenbroek) + */ + +#include "inc.h" + +FORWARD _PROTOTYPE( int write_file, (struct inode *ino, u64_t *posp, + size_t *countp, cp_grant_id_t *grantp) ); + +/*===========================================================================* + * write_file * + *===========================================================================*/ +PRIVATE int write_file(ino, posp, countp, grantp) +struct inode *ino; +u64_t *posp; +size_t *countp; +cp_grant_id_t *grantp; +{ +/* Write data or zeroes to a file, depending on whether a valid pointer to + * a data grant was provided. + */ + u64_t pos; + size_t count, size; + vir_bytes off; + char *ptr; + int r, chunk; + + assert(!IS_DIR(ino)); + + if ((r = get_handle(ino)) != OK) + return r; + + pos = *posp; + count = *countp; + + assert(count > 0); + + /* Use the buffer from libhgfs to eliminate extra copying. */ + size = hgfs_writebuf(&ptr); + off = 0; + + while (count > 0) { + chunk = MIN(count, size); + + if (grantp != NULL) { + r = sys_safecopyfrom(m_in.m_source, *grantp, + off, (vir_bytes) ptr, chunk, D); + + if (r != OK) + break; + } else { + /* Do this every time. We don't know what happens below. */ + memset(ptr, 0, chunk); + } + + if ((r = hgfs_write(ino->i_file, NULL, chunk, pos, FALSE)) <= 0) + break; + + count -= r; + off += r; + pos = add64u(pos, r); + } + + if (r < 0) + return r; + + *posp = pos; + *countp = off; + + return OK; +} + +/*===========================================================================* + * do_write * + *===========================================================================*/ +PUBLIC int do_write() +{ +/* Write data to a file. + */ + struct inode *ino; + u64_t pos; + size_t count; + cp_grant_id_t grant; + int r; + + if (state.read_only) + return EROFS; + + if ((ino = find_inode(m_in.REQ_INODE_NR)) == NIL_INODE) + return EINVAL; + + if (IS_DIR(ino)) return EISDIR; + + pos = make64(m_in.REQ_SEEK_POS_LO, m_in.REQ_SEEK_POS_HI); + count = m_in.REQ_NBYTES; + grant = m_in.REQ_GRANT; + + if (count <= 0) return EINVAL; + + if ((r = write_file(ino, &pos, &count, &grant)) != OK) + return r; + + m_out.RES_SEEK_POS_HI = ex64hi(pos); + m_out.RES_SEEK_POS_LO = ex64lo(pos); + m_out.RES_NBYTES = count; + + return OK; +} + +/*===========================================================================* + * do_ftrunc * + *===========================================================================*/ +PUBLIC int do_ftrunc() +{ +/* Change file size or create file holes. + */ + char path[PATH_MAX]; + struct inode *ino; + struct hgfs_attr attr; + u64_t start, end, delta; + size_t count; + int r; + + if (state.read_only) + return EROFS; + + if ((ino = find_inode(m_in.REQ_INODE_NR)) == NIL_INODE) + return EINVAL; + + if (IS_DIR(ino)) return EISDIR; + + start = make64(m_in.REQ_TRC_START_LO, m_in.REQ_TRC_START_HI); + end = make64(m_in.REQ_TRC_END_LO, m_in.REQ_TRC_END_HI); + + if (cmp64u(end, 0) == 0) { + /* Truncate or expand the file. */ + if ((r = verify_inode(ino, path, NULL)) != OK) + return r; + + attr.a_mask = HGFS_ATTR_SIZE; + attr.a_size = start; + + r = hgfs_setattr(path, &attr); + } else { + /* Write zeroes to the file. We can't create holes. */ + if (cmp64(end, start) <= 0) return EINVAL; + + delta = sub64(end, start); + + if (ex64hi(delta) != 0) return EINVAL; + + count = ex64lo(delta); + + r = write_file(ino, &start, &count, NULL); + } + + return r; +}