/* $Id: mpfsnode.C,v 1.21 2001/07/01 21:36:11 dm Exp $ */ /* * * Copyright (C) 1998 David Mazieres (dm@uun.org) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * */ #include "nfsmnt.h" #include "rxx.h" static dev_t baddev; #if 1 /* XXX - functions to work around g++ bugs */ typedef callback::ref xxx_lcb_t; static aclnt_cb xxx_wrap_1 (mpfsnode *c, void (mpfsnode::*f) (str, void *, xxx_lcb_t, clnt_stat), str a1, void *a2, xxx_lcb_t a3) { return wrap (c, f, a1, a2, a3); } static aclnt_cb xxx_wrap_2 (mpfsnode *c, void (mpfsnode::*f) (void *, cbid, mountarg *, clnt_stat), void *a1, cbid a2, mountarg *a3) { return wrap (c, f, a1, a2, a3); } #else #define xxx_wrap_1 wrap #define xxx_wrap_2 wrap #endif class mpfsref { mpfsnode *const n; mpfsref &operator= (const mpfsref &r); mpfsref (const mpfsref &r) : n (r.n) { n->incref (); } public: mpfsref (mpfsnode *nn) : n (nn) { n->incref (); } ~mpfsref () { n->decref (); } }; const char * basename (const char *s) { const char *p = strrchr (s, '/'); return p ? p + 1 : s; } str strip_double_slash (str s) { if (s[0] != '/' || s[1] != '/') return s; const char *p = s.cstr () + 1; while (p[1] == '/') p++; return str (p, s.cstr () + s.len () - p); } inline int umounterr (int e1, int e2) { if (!e1) return e2; if (!e2) return e1; if (e1 == EBUSY || e2 == EBUSY) return EBUSY; if (e1 == EINVAL) return e2; return e1; } mpfsnode::mpfsnode (str nm, mpfsnode::mpfsnode_type t, mpfsnode *par, ptr n, const nfsmnt_handle *hp, mpfsnode *mntdir, str hn) : fullpath (strip_double_slash (nm)), type (t), parent (par ? par : mntdir), mp (NULL), hostname (hn), refcnt (0), lock_flag (false), nf (n), dir (New mpfsdir), fh (t == LOCAL ? nfsmnt_handle () : *hp), fname (basename (fullpath)), attrvalid (false), devname ("") { assert (!par || !mntdir); assert (!mntdir || hostname); attrbase.init (); switch (type) { case NFS2: attr2.select (); assert (fh.size () == NFS_FHSIZE); nf->nfs2nodes.insert (this); break; case NFS3: attr3.select (); nf->nfs3nodes.insert (this); break; default: assert (!nf); break; } if (par) parent->dir->insert (this); else if (mntdir) parent->mp = this; } mpfsnode::~mpfsnode () { if (parent) { if (parent->mp == this) parent->mp = NULL; else parent->dir->remove (this); } switch (type) { case NFS2: nf->nfs2nodes.remove (this); break; case NFS3: nf->nfs3nodes.remove (this); break; default: break; } attrbase.destroy (); delete dir; } static rxx pathsplit ("^/*([^/]+)(/.*)?$"); mpfsnode * mpfsnode::lookup (str p) { if (!p || !pathsplit.search (p)) return this; else if (mp) return mp->lookup (p); else if (mpfsnode *n = dir->lookup(pathsplit[1])) return n->lookup (pathsplit[2]); else return NULL; } mpfsnode * mpfsnode::mkdir_local (str p) { if (type != LOCAL) return NULL; else if (!p || !pathsplit.search (p)) return this; else if (locked () || mp) return NULL; else { str first = pathsplit[1]; str rest = pathsplit[2]; if (mpfsnode *n = dir->lookup(first)) return n->mkdir_local (rest); else { n = New mpfsnode (fullpath << "/" << first, LOCAL, this); return n->mkdir_local (rest); } } } void mpfsnode::mkdir (str path, lcb_t cb) { if (!path || !pathsplit.search (path)) { (*cb) (this, 0); return; } str first = pathsplit[1]; str rest = pathsplit[2]; if (first == "" || first == "." || first == "..") (*cb) (NULL, EPERM); else if (locked ()) waiters.push_back (wrap (this, &mpfsnode::mkdir, path, cb)); else if (mp) mp->mkdir (path, cb); else if (type == UVFS) (*cb) (NULL, EINVAL); else if (mpfsnode *n = dir->lookup (first)) n->mkdir (rest, cb); else if (type == LOCAL) (*cb) (NULL, EPERM); else if (type == NFS2) { diropargs arg; assert (arg.dir.data.size () == fh.size ()); memcpy (arg.dir.data.base (), fh.base (), arg.dir.data.size ()); arg.name = first; if (ptr c = nf->mkclnt (2)) { lock (); diropres *resp = New diropres; c->call (NFSPROC_LOOKUP, &arg, resp, xxx_wrap_1 (this, &mpfsnode::lookupres, path, resp, cb), myauthunix); } else (*cb) (NULL, EIO); } else if (type == NFS3) { diropargs3 arg; arg.dir.data = fh; arg.name = first; if (ptr c = nf->mkclnt (3)) { lock (); lookup3res *resp = New lookup3res; c->call (NFSPROC3_LOOKUP, &arg, resp, xxx_wrap_1 (this, &mpfsnode::lookupres, path, resp, cb), myauthunix); } else (*cb) (NULL, EIO); } else panic ("mpfsnode: bad type\n"); } void mpfsnode::lookupres (str path, void *_resp, lcb_t cb, clnt_stat err) { pathsplit.search (path); str first = pathsplit[1]; str rest = pathsplit[2]; if (type == NFS2) { auto_ptr resp (static_cast (_resp)); if (err) (*cb) (NULL, EIO); else if (resp->status) (*cb) (NULL, resp->status); else { nfsmnt_handle h; h.setsize (NFS_FHSIZE); memcpy (h.base (), resp->reply->file.data.base (), h.size ()); mpfsnode *n = New mpfsnode (fullpath << "/" << first, NFS2, this, nf, &h); *n->attr2 = resp->reply->attributes; n->attrvalid = true; mpfsref r (n); n->mkdir (rest, cb); } } else if (type == NFS3) { auto_ptr resp (static_cast (_resp)); if (err) (*cb) (NULL, EIO); else if (resp->status) (*cb) (NULL, resp->status); else { mpfsnode *n = New mpfsnode (fullpath << "/" << first, NFS3, this, nf, &resp->resok->object.data); if (resp->resok->obj_attributes.present) { *n->attr3 = implicit_cast (*resp->resok->obj_attributes.attributes); // XXX - gcc 2.9 n->attrvalid = true; mpfsref r (n); n->mkdir (rest, cb); } else if (ptr c = nf->mkclnt (3)) { nfs_fh3 arg; getattr3res *resp = New getattr3res; arg.data = n->fh; n->lock (); c->call (NFSPROC3_GETATTR, &arg, resp, wrap (n, &mpfsnode::attr3mkdir, resp, rest, cb), myauthunix); } else (*cb) (NULL, EIO); } } else panic ("mpfsnode: bad type\n"); unlock (); } void mpfsnode::attr3mkdir (getattr3res *resp, str rest, lcb_t cb, clnt_stat err) { auto_ptr _resdel (resp); if (err) { unlock (); (*cb) (NULL, EIO); } else if (resp->status) { unlock (); (*cb) (NULL, resp->status); } else { *attr3 = implicit_cast (*resp->attributes); // XXX - gcc 2.9 attrvalid = true; mpfsref r (this); unlock (); mkdir (rest, cb); } } #ifdef HAVE_DEV_XFS void mpfsnode::mount_xfs (mountarg *a, str devname, cbid cb) { a->flags &= NMOPT_VALID; if (mp || dir->dir.size ()) { (*cb) (EBUSY, baddev); return; } else if (locked ()) { waiters.push_back (wrap (this, &mpfsnode::mount_xfs, a, devname, cb)); return; } lock (); vNew mpfsnode (fullpath, XFS, NULL, NULL, NULL, this, devname); int fds[2]; if (pipe (fds) < 0) { (*cb) (errno, baddev); delete mp; unlock (); return; } pid_t pid = afork (); switch (pid) { case -1: delete mp; (*cb) (errno, baddev); close (fds[0]); close (fds[1]); unlock (); break; case 0: close (fds[0]); domount_xfs (fullpath, devname, a->flags, fds[1]); panic ("domount_uvfs returned\n"); default: close (fds[1]); chldcb (pid, wrap (this, &mpfsnode::mountres, fds[0], cb)); break; } } #endif /* HAVE_DEV_XFS */ void mpfsnode::mount (mountarg *a, ref n, cbid cb) { a->flags &= NMOPT_VALID; if (n->sotype == SOCK_STREAM) a->flags |= NMOPT_TCP; if (mp || dir->dir.size ()) (*cb) (EBUSY, baddev); else if (locked ()) waiters.push_back (wrap (this, &mpfsnode::mount, a, n, cb)); #ifndef HAVE_NFS_V3 else if (a->flags & NMOPT_NFS3) (*cb) (EPROTONOSUPPORT, baddev); #endif /* !HAVE_NFS_V3 */ else if (!(a->flags & NMOPT_NFS3) && a->handle.size () != NFS_FHSIZE) (*cb) (EINVAL, baddev); else if (a->flags & NMOPT_NFS3) { if (ptr c = n->mkclnt (3)) { lock (); vNew mpfsnode (fullpath, NFS3, NULL, n, &a->handle, this, a->hostname); getattr3res *resp = New getattr3res; c->call (NFSPROC3_GETATTR, &a->handle, resp, xxx_wrap_2 (this, &mpfsnode::getattrres, resp, cb, a), myauthunix); } else (*cb) (EIO, baddev); } else { if (ptr c = n->mkclnt (2)) { lock (); vNew mpfsnode (fullpath, NFS2, NULL, n, &a->handle, this, a->hostname); attrstat *resp = New attrstat; c->call (NFSPROC_GETATTR, a->handle.base (), resp, xxx_wrap_2 (this, &mpfsnode::getattrres, resp, cb, a), myauthunix); } else (*cb) (EIO, baddev); } } void mpfsnode::getattrres (void *_resp, cbid cb, mountarg *a, clnt_stat err) { if (err) { if (mp->type == NFS2) delete static_cast (_resp); else delete static_cast (_resp); delete mp; (*cb) (EIO, baddev); unlock (); return; } else if (mp->type == NFS2) { attrstat *resp = static_cast (_resp); if (resp->status) { delete mp; (*cb) (resp->status, baddev); delete resp; unlock (); return; } *mp->attr2 = *resp->attributes; mp->attrvalid = true; delete resp; } else if (mp->type == NFS3) { getattr3res *resp = static_cast (_resp); if (resp->status) { delete mp; (*cb) (resp->status, baddev); delete resp; unlock (); return; } *mp->attr3 = implicit_cast (*resp->attributes); // XXX - 2.9 mp->attrvalid = true; delete resp; } int fds[2]; if (pipe (fds) < 0) { (*cb) (errno, baddev); delete mp; unlock (); return; } pid_t pid = afork (); switch (pid) { case -1: delete mp; (*cb) (errno, baddev); close (fds[0]); close (fds[1]); unlock (); break; case 0: close (fds[0]); domount (fullpath, &mp->nf->sin, &mp->fh, a->flags, a->hostname, fds[1]); panic ("domount returned\n"); default: close (fds[1]); chldcb (pid, wrap (this, &mpfsnode::mountres, fds[0], cb)); break; }; } void mpfsnode::mountres (int fd, cbid cb, int status) { int err = WIFEXITED (status) ? WEXITSTATUS (status) : EFAULT; if (err) { warn << "mount " << fullpath << ": " << strerror (err) << "\n"; delete mp; } else warn << "mounted " << fullpath << "\n"; unlock (); dev_t dev; bzero (&dev, sizeof (dev)); if (fd >= 0) { if (!err && read (fd, &dev, sizeof (dev)) == sizeof (dev)) { #if defined (major) && defined (minor) devname = strbuf (" (dev %d,%d)", major (dev), minor (dev)); #endif /* defined (major) && defined (minor) */ } close (fd); } (*cb) (err, dev); } void mpfsnode::remount (int flags, cbi cb) { if (!mp || (mp->type == NFS3) != bool (flags & NMOPT_NFS3)) { (*cb) (EINVAL); return; } if (locked ()) { waiters.push_back (wrap (this, &mpfsnode::remount, flags, cb)); return; } lock (); flags &= NMOPT_VALID; flags |= NMOPT_UPDATE; if (mp->nf->sotype == SOCK_STREAM) flags |= NMOPT_TCP; pid_t pid = afork (); switch (pid) { case -1: (*cb) (errno); unlock (); break; case 0: domount (fullpath, &mp->nf->sin, &mp->fh, flags, mp->hostname, -1); panic ("domount returned\n"); default: chldcb (pid, wrap (this, &mpfsnode::remountres, cb)); break; }; } void mpfsnode::remountres (cbi cb, int status) { int err = WIFEXITED (status) ? WEXITSTATUS (status) : EFAULT; if (err) warn << "remount " << fullpath << ": " << strerror (err) << "\n"; else warn << "remounted " << fullpath << "\n"; unlock (); (*cb) (err); } void mpfsnode::unmount (int flags, cbi cb) { if (locked ()) { (*cb) (EBUSY); return; } if (!mp) { (*cb) (EINVAL); return; } if (mp->dir->dir.size ()) { (*cb) (EAGAIN); return; } pid_t pid = afork (); switch (pid) { case -1: (*cb) (errno); break; case 0: doumount (fullpath, flags); panic ("doumount returned\n"); default: lock (); chldcb (pid, wrap (this, &mpfsnode::unmountres, cb, flags)); break; } } void mpfsnode::unmountres (cbi cb, int flags, int status) { int err = WIFEXITED (status) ? WEXITSTATUS (status) : EFAULT; if (err) { if (!(flags & NUOPT_NLOG)) warn << "unmount " << fullpath << devname << ": " << strerror (err) << "\n"; } else { warn << "unmounted " << fullpath << "\n"; delete mp; } unlock (); (*cb) (err); } void mpfsnode::unmountall (int flags, cbi cb) { if (locked ()) (*cb) (EBUSY); else if (mp) { lock (); mp->unmountall (flags, wrap (this, &mpfsnode::unmountallres1, flags, cb)); } else if (mpfsnode *n = dir->dir.first ()) { n->incref (); lock (); unmountallres2 (n, 0, flags, cb, 0); } else (*cb) (0); } void mpfsnode::unmountallres1 (int flags, cbi cb, int status) { if (status || !mp) { unlock (); (*cb) (status); } else { mpfsref r (this); unlock (); unmount (flags, cb); } } void mpfsnode::unmountallres2 (mpfsnode *n, int ostatus, int flags, cbi cb, int status) { ostatus = umounterr (ostatus, status); if (n) { mpfsnode *nn = dir->dir.next (n); if (nn) nn->incref (); n->unmountall (flags, wrap (this, &mpfsnode::unmountallres2, nn, ostatus, flags, cb)); n->decref (); } else { unlock (); (*cb) (ostatus); } } void mpfsnode::maybe_delete () { mpfsnode *p = parent; if (!locked () && !refcnt && p && this != p->mp && type != LOCAL && !mp && !dir->dir.size ()) { delete this; p->maybe_delete (); } } void mpfsnode::unlock () { assert (lock_flag); lock_flag = false; if (waiters.empty ()) maybe_delete (); else { incref (); (*waiters.pop_front ()) (); decref (); } }