#if 0 ifdef notdef * * POrtable Dodgy Filesystems in userland (hacK!) * alias podfuk (v2) * pretend you are venus cache manager, and export virtual files to kernel * * Copyright 1999 Pavel Machek, pavel@ucw.cz * * You can get more info at: * http://atrey.karlin.mff.cuni.cz/~pavel/podfuk/podfuk.html * * Distribute under GPL version 2 or later. * * Version 2.0 - it works, modulo strange things in tar archives. * Version 2.1 - tar archives + ftp are now fixed. * Version 2.2 - made it Makefile * Version 2.3 - attempt to make it deadlock free, no more complains for strange fids * Version 2.4 - make a bit more demonic * Version 2.5 - roll in changes by qrczak@knm.org.pl - handle uids right * Version 2.6 - add url, fix mknod, do not use ^#, fix paths * Version 2.7 - check return value for select + small fixes by Michael Hart * * Notice this file is *evil*. It is both valid c source and working * Makefile. You might want to ln -s podfuk.c Makefile. * * Edit MIDNIGHT= to point at root of mc sources, you need sources of * mc-4.5.5 or newer for this to work. (Youll miss file callback.h if * you use 4.5.30 or older - get it from homepage.) * * Edit KERNEL= to point include/ directory of your 2.2 kernel source * endif DEBUG_FLAG = -DDEBUG MIDNIGHT=/home/patb/projects/mc-4.5.39 KERNEL=/usr/src/linux/include VFS=$(MIDNIGHT)/vfs LINUX=/usr/src/linux all: podfuk podfuk: $(VFS)/libvfs.so podfuk.c gcc podfuk.c `glib-config --libs --cflags` -I$(MIDNIGHT) -I$(KERNEL) -g -L $(VFS) -lvfs -o podfuk $(DEBUG_FLAG) podfuk-static: podfuk.c gcc podfuk.c `glib-config --libs` -DNODAEMON -I$(MIDNIGHT) -I$(KERNEL) -g $(VFS)/tcputil.so $(VFS)/fish.so $(VFS)/ftpfs.so $(VFS)/mcfs.so $(VFS)/utilvfs.so $(VFS)/local.so $(VFS)/vfs.so $(VFS)/tar.so $(VFS)/sfs.so $(VFS)/names.so $(VFS)/container.so $(VFS)/extfs.so $(VFS)/util-alone.so $(VFS)/util.sor $(VFS)/utilunix.sor $(VFS)/direntry.so -o podfuk-static /dev/cfs0: mknod /dev/cfs0 c 67 5 /overlay: mkdir /overlay run: /dev/cfs0 /overlay podfuk { cd $(LINUX)/fs/coda; insmod coda.o; } sleep 1 { ./podfuk; } & sleep 1 mount /dev/cfs0 /overlay -tcoda clean: rm podfuk podfuk-static $(MIDNIGHT)/vfs/libvfs.so: echo 'Attempting to make libvfs.so from midnight' cd $(MIDNIGHT)/vfs make libvfs.so ifdef donotdefine #endif /* TTD: add timeouts for non-operative ftp sites a lookup request precipitates a login & read; is this neccessary for the root? check uid and gid are coming through the attributes merge common code for the creation cases (creat, mkdir) and deletion (rmdir, unlink) finish writing CODA_ACCESS keep a trail of temp files and BLOW THEM AWAY !!! is there another way to do create? yes, but I need to modify MC to give me the local filename of a file; also, I need to be able to set the has_changed bit in vfs */ /* Coda/Venus in a nutshell: * Coda is a filesystem, lives in the kernel * Venus is an app; podfuk is a replacement for venus * When a coda mount needs service, it sends a message * up to podfuk through a pipe * Coda/Podfuk both open /dev/cfs0 and make ioctl calls * Message includes ViceFid (coda file/dir handle) * which is: u32 volume-id, u32 node-id, u32 unique-num * Each file has an attribute struct: very similar to inode * * Typ sequence of calls to podfuk from coda: * file read: access(), open(), close() * (the read() is done through the local file) * dir listing: access(), open(), access(), close() * file write: lookup(file), access(parent), create(file), getattr(), open(), * close(), setattr() * * we have to hide the awful path names from the user * many apps cannot handle the #, : and @ * how about * /ftpsites/site_alias -> /ftpsites/.handle/site_name * and mount /dev/cfs0 /ftpsites/.handle -t coda * and the file ~/.podfukrc contains sections called * [site_name] * user=patb * pass=patb * path=chroot_path * * or, we could have a file /ftpsites/.config/site_name * * finally: * /mnt/ftp/aliasname * has placeholders such as * /mnt/ftp/corel -> /mnt/.mcvfs/ftp.corel.com * /mnt/.mcvfs is mounted as -t coda * /mnt/.mcvfsinf has config files, such as /mnt/.mcvfsinf/ftp.corel.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef USE_VFS #error Midnight was not configured to use vfs. Please configure it to use it. #endif #define PARANOIA 1 #ifdef DEBUG # define DEBUG_PRINT printf #else # define DEBUG_PRINT if (0) printf #endif #define MY_VOL 0x01234567 #define MY_VNODE 0xffffffff #define MY_INODE 0x12345678 extern int ftpfs_use_passive_connections; int ftpfs_login_timeout = 10; int cfs; struct v_list { char *real_name; char *local_name; char has_changed; char is_dir; /* timestamp ?? */ }; /* a linked list of the above objects */ GList *pList; int count = 0; /******************** shutdown and timeout **********************************/ void signal_handler(int signo) { mc_vfs_done(); exit(0); } /******************** config ************************************************/ void set_global_var( const char* section, const char* var, const char* val ) { if ( !strcmp( var, "ftpfs_use_passive_connections" ) ) ftpfs_use_passive_connections = ( val[0] == '1' ); if ( !strcmp( var, "logfile" ) && val[0] ) { ftpfs_set_debug( val ); printf("logfile set to %s\n",val); } if ( !strcmp( var, "ftpfs_login_timeout") ) ftpfs_login_timeout = atoi(val); } /* load and parse an ini file: handles sections, comments and params */ void load_profile( const char* config_file, void (*setter) (const char*, const char*, const char*) ) { char* section = 0; FILE* f = fopen( config_file, "r" ); if ( !f ) return; while (!feof(f)) { char buf[250]; char *p = buf; fgets(buf,250,f); p = g_strstrip(buf); if (p[0] == '[') { g_free(section); section = g_strdup(g_strdelimit(p+1,"]",'\0')); //printf("got section header [%s]\n", section); } else if (p[0] == '#') { //printf("got a comment <%s>\n",p+1); } else { char **pp; pp = g_strsplit(buf,"=",1); if (pp[0] && pp[1]) (*setter)( section, pp[0], pp[1] ); //printf("got strings <%s> and <%s>\n",g_strstrip(pp[0]),pp[1] ? (g_strstrip(pp[1])) : ""); g_strfreev(pp); } } g_free(section); fclose(f); } char * look_name( ViceFid id ) { if ((id.Volume != MY_VOL) || (id.Vnode != MY_VNODE)) { printf( "Bad handle passed %x/%x/%x\n", id.Volume, id.Vnode, id.Unique ); exit(1); } if (PARANOIA) { GList *pWalk; for( pWalk = pList; pWalk; pWalk = g_list_next(pWalk) ) { if (pWalk->data == (gpointer) id.Unique) break; } if (!pWalk) { printf( "Bad handle passed %x/%x/%x\n", id.Volume, id.Vnode, id.Unique ); exit(1); } } /* paranoia */ return ((struct v_list*)id.Unique)->real_name; } ViceFid alloc_vfid( char *name ) { ViceFid res; struct v_list *h = g_new0(struct v_list,1); pList = g_list_append(pList, h); h->real_name = strdup(name); count++; res.Volume = MY_VOL; res.Vnode = MY_VNODE; res.Unique = (long) h; DEBUG_PRINT( "new vfid created at %p (file <%s>)\n",h,h->real_name ); return res; } void delete_name( const char * real_name ) { GList *pWalk; /* at this point, the paranoid would check for a dirty file and write it out */ for( pWalk = pList; pWalk; pWalk = g_list_next(pWalk) ) { if (!strcmp( ((struct v_list*)pWalk->data)->real_name, real_name)) { pList = g_list_remove( pList, pWalk->data ); return; } } printf( "failure trying to delete the file %s\n", real_name ); } inline char * get_local_copy_new( ViceFid id) { int h; struct v_list* v = (struct v_list*)id.Unique; v->local_name = tempnam( NULL, "podfu" ); h = open(v->local_name, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0666); if (h<0) return v->local_name = 0; write(h, &h, 0); close(h); v->has_changed = 1; return v->local_name; } inline char * get_local_copy( ViceFid id, int flags ) { struct v_list* v = (struct v_list*)id.Unique; /* I would normally put an assert here, but look_name() was called already */ /* truncated on writeonly files do not need an ftp read first */ if ((flags & C_O_READ) || !(flags & C_O_TRUNC)) { if (flags & C_O_WRITE) v->has_changed = 1; return v->local_name = mc_getlocalcopy(v->real_name); } else return get_local_copy_new(id);; } void write_dir_entry( int handle, char *name ) { struct venus_dirent ent; int len; static int ino = MY_INODE; ent.d_fileno = ino++; ent.d_type = CDT_REG; /* should we report dirs & symlinks now ?? */ ent.d_namlen = len = strlen(name); strcpy(ent.d_name, name); len += ((char *) &ent.d_name - (char *) &ent); ent.d_reclen = len; if (write(handle, &ent, len)<0) DEBUG_PRINT( "error writing dirent (%m)" ); DEBUG_PRINT( "[%s]", name ); } char * get_dir_copy( ViceFid id ) { struct v_list* v = (struct v_list*) id.Unique; DIR *dir = mc_opendir(v->real_name); char *cache; int handle; struct dirent *ent1; char zeros[128] = {0,0, }; if (!dir) return NULL; DEBUG_PRINT( "converting dir to file " ); cache = tempnam (NULL, "dir"); handle = open (cache, O_WRONLY | O_CREAT | O_EXCL, 0600); if (handle == -1) { DEBUG_PRINT("can not open temp file\n" ); closedir(dir); /* ?? should this be mc_closedir() ?? */ free(cache); /* possible memory leak fixed */ return NULL; } DEBUG_PRINT( "%s:", cache ); while (ent1 = mc_readdir(dir)) write_dir_entry(handle, ent1->d_name); write(handle, zeros, 2); DEBUG_PRINT(" "); close(handle); mc_closedir(dir); v->is_dir = 1; return v->local_name = cache; } void unget_local_copy( ViceFid id ) { struct v_list* v = (struct v_list*) id.Unique; int ret_val; if (v->local_name) { if (v->has_changed) DEBUG_PRINT( "saving the file %s ", v->local_name); /* NOTE: if we give mc_ungetlocalcopy() the exact correct localfile name, it will not free() the 2nd parameter string */ if (v->is_dir) { unlink( v->local_name ); } else mc_ungetlocalcopy(v->real_name, v->local_name, v->has_changed); v->has_changed = 0; /* re-enable this line once we fix mc_ungetlocalcopy */ //g_free(v->local_name); v->local_name = 0; } else { DEBUG_PRINT( "" ); } } /* convert mc vfs type/attr to coda type/attr */ int st2type(struct stat *s) { if (S_ISDIR(s->st_mode)) return C_VDIR; if (S_ISREG(s->st_mode)) return C_VREG; if (S_ISBLK(s->st_mode)) return C_VBLK; if (S_ISCHR(s->st_mode)) return C_VCHR; if (S_ISLNK(s->st_mode)) return C_VLNK; if (S_ISSOCK(s->st_mode)) return C_VSOCK; if (S_ISFIFO(s->st_mode)) return C_VFIFO; DEBUG_PRINT( "Unknown type\n" ); return C_VNON; } void st2attr(struct stat *s, struct coda_vattr *a) { static int id = 0; bzero(a, sizeof(struct coda_vattr)); a->va_type = st2type(s); #define COPY(x) a->va_##x = s->st_##x; COPY(mode); DEBUG_PRINT( "(mode = %o)", s->st_mode ); COPY(nlink); COPY(uid); COPY(gid); a->va_fileid = id++; COPY(size); a->va_blocksize = 1024; /* s->st_blksize; */ a->va_atime.tv_nsec = a->va_ctime.tv_nsec = a->va_mtime.tv_nsec = 0; a->va_atime.tv_sec = s->st_atime; a->va_mtime.tv_sec = s->st_mtime; a->va_ctime.tv_sec = s->st_ctime; a->va_gen = 0; a->va_flags = 0; a->va_bytes = s->st_blocks * s->st_blksize; a->va_filerev = 0; } /* Poor protection against deadlocks when podfuk is mounted on /overlay (right protection is VFS_NO_LOCALHASH, which is unfortunately in new midnights, only). In fact, "new midnight" currently means my own tree at my home machine :-) */ int legal_name(char *name) { if (!strncmp (name, "/mnt/.mcvfs", 11) || (strchr(name, '#') && !vfs_rosplit(name))) return 0; return 1; } int main(void) { char buf[2048]; setvbuf(stdout, NULL, _IONBF, 0); cfs = open( "/dev/cfs0", O_RDWR ); if (cfs == -1) { DEBUG_PRINT( "Error opening cfs0: %m\n" ); exit(1); } #ifndef NODAEMON chdir("/"); if (fork()) { exit(0); } setsid(); /* to guarantee effect, the man page says this should be performed after a fork, as the child... */ DEBUG_PRINT( "podfuk spawned as %d, ", getpid() ); #endif DEBUG_PRINT( "Opened cfs0\n" ); load_profile( "/etc/.podfukrc", set_global_var ); mc_vfs_init(); signal(SIGTERM, signal_handler); signal(SIGINT, signal_handler); vfs_flags |= FL_ALWAYS_MAGIC; //#ifdef FL_NO_LOCALHASH // vfs_flags |= FL_NO_LOCALHASH; /* Avoid local deadlock when someone accesses /#ahoj#utar */ //#else //#warning Sorry, your midnight is old. It may deadlock under some uses. //#endif while(1) { char in_buf[sizeof(union inputArgs) + 256]; union inputArgs *req = (union inputArgs*) &in_buf[0]; union outputArgs rep; struct stat st; int msg; int size; char *name; fd_set rfds; struct timeval tv; int retval; /* Watch stdin (fd 0) to see when it has input. */ FD_ZERO(&rfds); FD_SET(cfs, &rfds); /* Wait up to five seconds. */ tv.tv_sec = 5; tv.tv_usec = 0; retval = select(cfs+1, &rfds, NULL, NULL, &tv); if (retval == -1) { DEBUG_PRINT( "select failed: %m\n" ); exit(1); } vfs_timeout_handler(); /* Don't rely on the value of tv now! */ msg = read(cfs, req, sizeof(*req) + 256); if (msg == -1) continue; DEBUG_PRINT( "got %3.d byte command: opcode = %2.d ", msg, req->ih.opcode ); // printf( " (un=%d,pid=%d,pgid=%d) ", req.ih.unique, req.ih.pid, req.ih.pgid ); // printf( " (uid=%d,euid=%d,suid=%d,fsuid=%d) ",req.ih.cred.cr_uid,req.ih.cred.cr_euid,req.ih.cred.cr_suid,req.ih.cred.cr_fsuid ); setfsgid (req->ih.cred.cr_fsgid); vfs_gid = req->ih.cred.cr_fsgid; setfsuid (req->ih.cred.cr_fsuid); vfs_uid = req->ih.cred.cr_fsuid; rep.oh.opcode = req->ih.opcode; rep.oh.unique = req->ih.unique; rep.oh.result = ENOSYS; size = sizeof(rep.oh); #define CMD(x) rep.oh.result = 0; size = sizeof(rep.coda_##x); DEBUG_PRINT( "%.10s",#x ); #define CMD_NOREP(x) rep.oh.result = 0; DEBUG_PRINT( "%.10s",#x ); //#define DUMP(x) DEBUG_PRINT( "(%x/%x/%x:%s)", req->coda_##x.VFid.Volume, req->coda_##x.VFid.Vnode, req->coda_##x.VFid.Unique, look_name(req->coda_##x.VFid)); #define DUMP(x) DEBUG_PRINT( " (base:%s)", look_name(req->coda_##x.VFid)); #define DUMP_NAME(x) DEBUG_PRINT( " (base:%s\tname:%s)", look_name(req->coda_##x.VFid), (char*)req + req->coda_##x.name); #define STAT(x) if (!legal_name(x)) {rep.oh.result = ENOENT; break; } else if (mc_stat(x, &st) == -1) { rep.oh.result = errno; DEBUG_PRINT( "file %s probably does not exist.",x ); break; } switch (req->ih.opcode) { case CODA_ROOT: CMD(root); DEBUG_PRINT( ": " ); rep.coda_root.VFid = alloc_vfid( "/" ); break; /********************* file props & access *******************/ case CODA_GETATTR: CMD(getattr); DUMP(getattr); DEBUG_PRINT( ": " ); name = look_name(req->coda_getattr.VFid); STAT(name); /* fills in st */ st2attr( &st, &rep.coda_getattr.attr ); break; case CODA_SETATTR: CMD_NOREP(setattr); name = look_name(req->coda_setattr.VFid); DEBUG_PRINT( " try to set mode to %o, mdate to %x and owner to %d: ", req->coda_setattr.attr.va_mode, req->coda_setattr.attr.va_mtime.tv_sec, req->coda_setattr.attr.va_uid ); if ( req->coda_setattr.attr.va_mode != (u_short) -1) mc_chmod( name, req->coda_setattr.attr.va_mode ); /* cannot change owner, group or dates .......*/ rep.oh.result = 0; /* lie to the caller !! */ break; case CODA_ACCESS: /* this needs to be beefed up !! */ rep.oh.result = 0; DEBUG_PRINT( "access" ); DUMP(access); DEBUG_PRINT( "flags:%x ", req->coda_access.flags ); break; case CODA_LOOKUP: /* !! ttd: search for existing VFid's instead of making many new ones */ CMD(lookup); DUMP_NAME(lookup); name = ((char *) req) + req->coda_lookup.name; DEBUG_PRINT( "flags=%d: ", req->coda_lookup.flags ); sprintf( buf, "%s/%s", look_name(req->coda_lookup.VFid), name ); DEBUG_PRINT( "(stat: %s)", buf ); STAT(buf); /* fills in st */ rep.coda_lookup.VFid = alloc_vfid(buf); rep.coda_lookup.vtype = st2type(&st); break; /************************ open, close, create & mkdir ******************/ case CODA_OPEN: CMD(open); DUMP(open); DEBUG_PRINT( "flags:%#x ", req->coda_open.flags ); name = look_name(req->coda_open.VFid); STAT(name); if (S_ISDIR(st.st_mode)) { DEBUG_PRINT( "[open request on a directory]" ); name = get_dir_copy(req->coda_open.VFid); } else name = get_local_copy(req->coda_open.VFid,req->coda_open.flags); if (!name || (stat(name, &st)==-1)) { rep.oh.result = errno; break; } rep.coda_open.dev = st.st_dev; rep.coda_open.inode = st.st_ino; break; case CODA_CLOSE: CMD_NOREP(close); DUMP(close); name = look_name(req->coda_open.VFid);/*!!*/ unget_local_copy(req->coda_open.VFid); /* !! how do we get error codes back to caller ?? */ DEBUG_PRINT( " the current VFid list has %d elements\n", g_list_length( pList ) ); break; case CODA_CREATE: CMD(create); DUMP_NAME(create); name = ((char *) req) + req->coda_create.name; sprintf( buf, "%s/%s", look_name(req->coda_mkdir.VFid), name ); DEBUG_PRINT( "(create_name: %s)", buf ); /* ?? try a 0 byte write ??*/ #if 0 name = get_local_copy_new(rep.coda_create.VFid); {int i; i = open(name, O_RDWR | O_CREAT | O_TRUNC | O_EXCL, 0600); write(i,&i,1); close(i); unget_local_copy(rep.coda_create.VFid); } ((struct v_list*)(rep.coda_create.VFid.Unique))->has_changed = 1; #endif { int i; i = mc_open(buf, O_WRONLY | O_CREAT, 0600); if (i>=0) { mc_write(i,(void*)&i,1); mc_close(i); rep.coda_create.VFid = alloc_vfid(buf); } else rep.oh.result = errno; } rep.coda_create.attr = req->coda_create.attr; break; case CODA_MKDIR: CMD(mkdir); DUMP_NAME(mkdir); /* note: we only get the entry name; must concat with VFid */ name = ((char *) req) + req->coda_mkdir.name; DEBUG_PRINT( "(mkdir: %s)", buf ); if ( mc_mkdir( buf, req->coda_mkdir.attr.va_mode ) == 0) { rep.coda_mkdir.VFid = alloc_vfid(buf); rep.coda_mkdir.attr = req->coda_mkdir.attr; } else rep.oh.result = errno; break; /********************* deletions & rename ******************/ case CODA_RMDIR: /* !! there is common code in rmdir, remove and mkdir */ CMD_NOREP(rmdir); DUMP_NAME(rmdir); /* note: we only get the entry name; must concat with VFid */ name = ((char *) req) + req->coda_rmdir.name; sprintf( buf, "%s/%s", look_name(req->coda_rmdir.VFid), name ); DEBUG_PRINT( "(rmdir: %s)", buf ); if ( mc_rmdir( buf ) == 0) { rep.oh.result = 0; delete_name( buf ); } else rep.oh.result = errno; break; case CODA_REMOVE: CMD_NOREP(remove); DUMP_NAME(mkdir); /* note: we only get the entry name; must concat with VFid */ name = ((char *) req) + req->coda_remove.name; DEBUG_PRINT( "name=%s, mode=%#o: ", name ); sprintf( buf, "%s/%s", look_name(req->coda_remove.VFid), name ); DEBUG_PRINT( "(remove: %s)", buf ); if ( mc_unlink( buf ) == 0) { rep.oh.result = 0; delete_name( buf ); } else rep.oh.result = errno; break; case CODA_RENAME: CMD_NOREP(rename); { char buf2[256]; name = ((char *) req) + req->coda_rename.srcname; sprintf( buf, "%s/%s", look_name(req->coda_rename.sourceFid), name ); name = ((char *) req) + req->coda_rename.destname; sprintf( buf2, "%s/%s", look_name(req->coda_rename.destFid), name ); DEBUG_PRINT( "src name=%s, dest name %s: ", buf, buf2 ); if ( mc_rename( buf, buf2 ) == 0) { rep.oh.result = 0; delete_name( buf ); } else rep.oh.result = errno; } break; default: DEBUG_PRINT("unimplemented coda call"); break; } DEBUG_PRINT( "returning %d, %d bytes\n", rep.oh.result, size ); msg = write(cfs, &rep, size); fflush(stdout); setegid (0); vfs_gid = 0; seteuid (0); vfs_uid = 0; } /* while */ } /* Leave this in place - this is here so that code still is valid Makefile. */ #if 0 endif #endif