/* $NetBSD: fstest_nfs.c,v 1.13 2024/09/08 09:36:52 rillig Exp $ */ /* * Copyright (c) 2010 The NetBSD Foundation, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "h_fsmacros.h" #include "mount_nfs.h" #include "../../net/config/netconfig.c" #define SERVERADDR "10.3.2.1" #define SERVERROADDR "10.4.2.1" #define CLIENTADDR "10.3.2.2" #define CLIENTROADDR "10.4.2.2" #define NETNETMASK "255.255.255.0" #define EXPORTPATH "/myexport" static void childfail(int status) { atf_tc_fail("child died"); } /* fork rump nfsd, configure interface */ static int donewfs(const atf_tc_t *tc, void **argp, const char *image, off_t size, void *fspriv) { const char *srcdir; char *nfsdargv[16]; char nfsdpath[MAXPATHLEN]; char imagepath[MAXPATHLEN]; char ethername[MAXPATHLEN], ethername_ro[MAXPATHLEN]; char ifname[IFNAMSIZ], ifname_ro[IFNAMSIZ]; char cwd[MAXPATHLEN]; struct nfstestargs *args; pid_t childpid; int pipes[2]; int devnull; /* * First, we start the nfs service. */ srcdir = atf_tc_get_config_var(tc, "srcdir"); snprintf(nfsdpath, sizeof nfsdpath, "%s/../nfs/nfsservice/rumpnfsd", srcdir); snprintf(ethername, sizeof ethername, "/%s/%s.etherbus", getcwd(cwd, sizeof(cwd)), image); snprintf(ethername_ro, sizeof ethername_ro, "%s_ro", ethername); snprintf(imagepath, sizeof imagepath, "/%s/%s", cwd, image); nfsdargv[0] = nfsdpath; nfsdargv[1] = ethername; nfsdargv[2] = ethername_ro; nfsdargv[3] = __UNCONST(SERVERADDR); nfsdargv[4] = __UNCONST(SERVERROADDR); nfsdargv[5] = __UNCONST(NETNETMASK); nfsdargv[6] = __UNCONST(EXPORTPATH); nfsdargv[7] = imagepath; nfsdargv[8] = NULL; signal(SIGCHLD, childfail); if (pipe(pipes) == -1) return errno; switch ((childpid = fork())) { case 0: if (chdir(dirname(nfsdpath)) == -1) err(1, "chdir"); close(pipes[0]); if (dup2(pipes[1], 3) == -1) err(1, "dup2"); execvp(nfsdargv[0], nfsdargv); err(1, "execvp"); case -1: return errno; default: close(pipes[1]); break; } /* * Ok, nfsd has been run. The following sleep helps with the * theoretical problem that nfsd can't start fast enough to * process our mount request and we end up doing a timeout * before the mount. This would take several seconds. So * try to make sure nfsd is up&running already at this stage. */ if (read(pipes[0], &devnull, 4) == -1) return errno; /* * Configure our networking interface. */ rump_init(); netcfg_rump_makeshmif(ethername, ifname); netcfg_rump_if(ifname, CLIENTADDR, NETNETMASK); netcfg_rump_makeshmif(ethername_ro, ifname_ro); netcfg_rump_if(ifname_ro, CLIENTROADDR, NETNETMASK); /* * That's it. The rest is done in mount, since we don't have * the mountpath available here. */ args = malloc(sizeof(*args)); if (args == NULL) return errno; memset(args, 0, sizeof(*args)); args->ta_childpid = childpid; strcpy(args->ta_ethername, ethername); *argp = args; return 0; } int nfs_fstest_newfs(const atf_tc_t *tc, void **argp, const char *image, off_t size, void *fspriv) { return donewfs(tc, argp, image, size, fspriv); } int nfsro_fstest_newfs(const atf_tc_t *tc, void **argp, const char *image, off_t size, void *fspriv) { return donewfs(tc, argp, image, size, fspriv); } /* mount the file system */ static int domount(const atf_tc_t *tc, void *arg, const char *serverpath, const char *path, int flags) { char canon_dev[MAXPATHLEN], canon_dir[MAXPATHLEN]; const char *nfscliargs[] = { "nfsclient", serverpath, path, NULL, }; struct nfs_args args; int mntflags; if (rump_sys_mkdir(path, 0777) == -1) return errno; /* XXX: atf does not reset values */ optind = 1; opterr = 1; /* * We use nfs parseargs here, since as a side effect it * takes care of the RPC hulabaloo. */ mount_nfs_parseargs(__arraycount(nfscliargs)-1, __UNCONST(nfscliargs), &args, &mntflags, canon_dev, canon_dir); if (rump_sys_mount(MOUNT_NFS, path, flags, &args, sizeof(args)) == -1) { return errno; } return 0; } int nfs_fstest_mount(const atf_tc_t *tc, void *arg, const char *path, int flags) { return domount(tc, arg, SERVERADDR ":" EXPORTPATH, path, flags); } /* * This is where the magic happens! * * If we are mounting r/w, do the normal thing. However, if we are * doing a r/o mount, switch use the r/o server export address * and do a r/w mount. This way we end up testing the r/o export policy * of the server! (yes, slightly questionable semantics, but at least * we notice very quickly if our assumption is broken in the future ;) */ int nfsro_fstest_mount(const atf_tc_t *tc, void *arg, const char *path, int flags) { if (flags & MNT_RDONLY) { flags &= ~MNT_RDONLY; return domount(tc, arg, SERVERROADDR":"EXPORTPATH, path, flags); } else { return domount(tc, arg, SERVERADDR":"EXPORTPATH, path, flags); } } static int dodelfs(const atf_tc_t *tc, void *arg) { /* * XXX: no access to "args" since we're called from "cleanup". * Trust atf to kill nfsd process and remove etherfile. */ #if 0 /* * It's highly expected that the child will die next, so we * don't need that information anymore thank you very many. */ signal(SIGCHLD, SIG_IGN); /* * Just KILL it. Sending it SIGTERM first causes it to try * to send some unmount RPCs, leading to sticky situations. */ kill(args->ta_childpid, SIGKILL); wait(&status); /* remove ethernet bus */ if (unlink(args->ta_ethername) == -1) atf_tc_fail_errno("unlink ethername"); #endif return 0; } int nfs_fstest_delfs(const atf_tc_t *tc, void *arg) { return dodelfs(tc, arg); } int nfsro_fstest_delfs(const atf_tc_t *tc, void *arg) { return dodelfs(tc, arg); } static int dounmount(const atf_tc_t *tc, const char *path, int flags) { int status, i, sverrno; /* * NFS handles sillyrenames in a workqueue. Some of them might * be still in the queue even if all user activity has ceased. * We try to unmount for 2 seconds to give them a chance * to flush out. * * PR kern/43799 */ for (i = 0; i < 20; i++) { if ((status = rump_sys_unmount(path, flags)) == 0) break; sverrno = errno; if (sverrno != EBUSY) break; usleep(100000); } if (status == -1) return sverrno; if (rump_sys_rmdir(path) == -1) return errno; return 0; } int nfs_fstest_unmount(const atf_tc_t *tc, const char *path, int flags) { return dounmount(tc, path, flags); } int nfsro_fstest_unmount(const atf_tc_t *tc, const char *path, int flags) { return dounmount(tc, path, flags); }