X-Git-Url: https://git.decadent.org.uk/gitweb/?p=nfs-utils.git;a=blobdiff_plain;f=utils%2Fnfsd%2Fnfssvc.c;h=683008e291c6bff1016b365789d3401b5380604c;hp=0a7546a3c6f53544f65b65a564dc16a4f7308577;hb=014e00dfaea0efc92150e2aedc5ca43aa337545e;hpb=6f25394cb5651e7e44cc3fc0b2b4b2ccba8c3625 diff --git a/utils/nfsd/nfssvc.c b/utils/nfsd/nfssvc.c index 0a7546a..683008e 100644 --- a/utils/nfsd/nfssvc.c +++ b/utils/nfsd/nfssvc.c @@ -10,115 +10,269 @@ #include #endif +#include #include +#include #include #include +#include #include #include #include +#include #include "nfslib.h" #include "xlog.h" -#define NFSD_PORTS_FILE "/proc/fs/nfsd/portlist" -#define NFSD_VERS_FILE "/proc/fs/nfsd/versions" -#define NFSD_THREAD_FILE "/proc/fs/nfsd/threads" +#ifndef NFSD_FS_DIR +#define NFSD_FS_DIR "/proc/fs/nfsd" +#endif + +#define NFSD_PORTS_FILE NFSD_FS_DIR "/portlist" +#define NFSD_VERS_FILE NFSD_FS_DIR "/versions" +#define NFSD_THREAD_FILE NFSD_FS_DIR "/threads" + +/* + * declaring a common static scratch buffer here keeps us from having to + * continually thrash the stack. The value of 128 bytes here is really just a + * SWAG and can be increased if necessary. It ought to be enough for the + * routines below however. + */ +char buf[128]; -static void -nfssvc_setfds(int port, unsigned int ctlbits, char *haddr) +/* + * Using the "new" interfaces for nfsd requires that /proc/fs/nfsd is + * actually mounted. Make an attempt to mount it here if it doesn't appear + * to be. If the mount attempt fails, no big deal -- fall back to using nfsctl + * instead. + */ +void +nfssvc_mount_nfsdfs(char *progname) { - int fd, n, on=1; - char buf[BUFSIZ]; - int udpfd = -1, tcpfd = -1; - struct sockaddr_in sin; + int err; + struct stat statbuf; - fd = open(NFSD_PORTS_FILE, O_RDONLY); - if (fd < 0) + err = stat(NFSD_THREAD_FILE, &statbuf); + if (err == 0) return; - n = read(fd, buf, BUFSIZ); - close(fd); - if (n != 0) + + if (errno != ENOENT) { + xlog(L_ERROR, "Unable to stat %s: errno %d (%m)", + NFSD_THREAD_FILE, errno); return; - /* there are no ports currently open, so it is safe to - * try to open some and pass them through. - * Note: If the user explicitly asked for 'udp', then - * we should probably check if that is open, and should - * open it if not. However we don't yet. All sockets - * have to be opened when the first daemon is started. + } + + /* + * this call can return an error if modprobe is set up to automatically + * mount nfsdfs when nfsd.ko is plugged in. So, ignore the return + * code from it and just check for the "threads" file afterward. */ + system("/bin/mount -t nfsd nfsd " NFSD_FS_DIR " >/dev/null 2>&1"); + + err = stat(NFSD_THREAD_FILE, &statbuf); + if (err == 0) + return; + + xlog(L_WARNING, "Unable to access " NFSD_FS_DIR " errno %d (%m)." + "\nPlease try, as root, 'mount -t nfsd nfsd " NFSD_FS_DIR + "' and then restart %s to correct the problem", errno, progname); + + return; +} + +/* + * Are there already sockets configured? If not, then it is safe to try to + * open some and pass them through. + * + * Note: If the user explicitly asked for 'udp', then we should probably check + * if that is open, and should open it if not. However we don't yet. All + * sockets have to be opened when the first daemon is started. + */ +int +nfssvc_inuse(void) +{ + int fd, n; + + fd = open(NFSD_PORTS_FILE, O_RDONLY); + + /* problem opening file, assume that nothing is configured */ + if (fd < 0) + return 0; + + n = read(fd, buf, sizeof(buf)); + close(fd); + + xlog(D_GENERAL, "knfsd is currently %s", (n > 0) ? "up" : "down"); + + return (n > 0); +} + +static int +nfssvc_setfds(const struct addrinfo *hints, const char *node, const char *port) +{ + int fd, on = 1, fac = L_ERROR; + int sockfd = -1, rc = 0; + struct addrinfo *addrhead = NULL, *addr; + char *proto, *family; + + /* + * if file can't be opened, then assume that it's not available and + * that the caller should just fall back to the old nfsctl interface + */ fd = open(NFSD_PORTS_FILE, O_WRONLY); if (fd < 0) - return; - sin.sin_family = AF_INET; - sin.sin_port = htons(port); - sin.sin_addr.s_addr = inet_addr(haddr); - - if (NFSCTL_UDPISSET(ctlbits)) { - udpfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (udpfd < 0) { - xlog(L_ERROR, "unable to create UDP socket: " - "errno %d (%m)", errno); - exit(1); - } - if (bind(udpfd, (struct sockaddr *)&sin, sizeof(sin)) < 0){ - xlog(L_ERROR, "unable to bind UDP socket: " - "errno %d (%m)", errno); - exit(1); - } + return 0; + + switch(hints->ai_family) { + case AF_INET: + family = "inet"; + break; +#ifdef IPV6_SUPPORTED + case AF_INET6: + family = "inet6"; + break; +#endif /* IPV6_SUPPORTED */ + default: + xlog(L_ERROR, "Unknown address family specified: %d\n", + hints->ai_family); + rc = EAFNOSUPPORT; + goto error; } - if (NFSCTL_TCPISSET(ctlbits)) { - tcpfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (tcpfd < 0) { - xlog(L_ERROR, "unable to create TCP socket: " - "errno %d (%m)", errno); - exit(1); + rc = getaddrinfo(node, port, hints, &addrhead); + if (rc == EAI_NONAME && !strcmp(port, "nfs")) { + snprintf(buf, sizeof(buf), "%d", NFS_PORT); + rc = getaddrinfo(node, buf, hints, &addrhead); + } + + if (rc != 0) { + xlog(L_ERROR, "unable to resolve %s:%s to %s address: " + "%s", node ? node : "ANYADDR", port, family, + rc == EAI_SYSTEM ? strerror(errno) : + gai_strerror(rc)); + goto error; + } + + addr = addrhead; + while(addr) { + /* skip non-TCP / non-UDP sockets */ + switch(addr->ai_protocol) { + case IPPROTO_UDP: + proto = "UDP"; + break; + case IPPROTO_TCP: + proto = "TCP"; + break; + default: + addr = addr->ai_next; + continue; } - if (setsockopt(tcpfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) { - xlog(L_ERROR, "unable to set SO_REUSEADDR: " - "errno %d (%m)", errno); - exit(1); + + xlog(D_GENERAL, "Creating %s %s socket.", family, proto); + + /* open socket and prepare to hand it off to kernel */ + sockfd = socket(addr->ai_family, addr->ai_socktype, + addr->ai_protocol); + if (sockfd < 0) { + if (errno == EAFNOSUPPORT) + xlog(L_NOTICE, "address family %s not " + "supported by protocol %s", + family, proto); + else + xlog(L_ERROR, "unable to create %s %s socket: " + "errno %d (%m)", family, proto, errno); + rc = errno; + goto error; } - if (bind(tcpfd, (struct sockaddr *)&sin, sizeof(sin)) < 0){ - xlog(L_ERROR, "unable to bind TCP socket: " - "errno %d (%m)", errno); - exit(1); +#ifdef IPV6_SUPPORTED + if (addr->ai_family == AF_INET6 && + setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on))) { + xlog(L_ERROR, "unable to set IPV6_V6ONLY: " + "errno %d (%m)\n", errno); + rc = errno; + goto error; + } +#endif /* IPV6_SUPPORTED */ + if (addr->ai_protocol == IPPROTO_TCP && + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) { + xlog(L_ERROR, "unable to set SO_REUSEADDR on %s " + "socket: errno %d (%m)", family, errno); + rc = errno; + goto error; } - if (listen(tcpfd, 64) < 0){ + if (bind(sockfd, addr->ai_addr, addr->ai_addrlen)) { + xlog(L_ERROR, "unable to bind %s %s socket: " + "errno %d (%m)", family, proto, errno); + rc = errno; + goto error; + } + if (addr->ai_protocol == IPPROTO_TCP && listen(sockfd, 64)) { xlog(L_ERROR, "unable to create listening socket: " "errno %d (%m)", errno); - exit(1); - } - } - if (udpfd >= 0) { - snprintf(buf, BUFSIZ,"%d\n", udpfd); - if (write(fd, buf, strlen(buf)) != strlen(buf)) { - xlog(L_ERROR, - "writing fds to kernel failed: errno %d (%m)", - errno); + rc = errno; + goto error; } - close(fd); - fd = -1; - } - if (tcpfd >= 0) { + if (fd < 0) fd = open(NFSD_PORTS_FILE, O_WRONLY); - snprintf(buf, BUFSIZ,"%d\n", tcpfd); - if (write(fd, buf, strlen(buf)) != strlen(buf)) { - xlog(L_ERROR, - "writing fds to kernel failed: errno %d (%m)", - errno); + + if (fd < 0) { + xlog(L_ERROR, "couldn't open ports file: errno " + "%d (%m)", errno); + goto error; } + + snprintf(buf, sizeof(buf), "%d\n", sockfd); + if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf)) { + /* + * this error may be common on older kernels that don't + * support IPv6, so turn into a debug message. + */ + if (errno == EAFNOSUPPORT) + fac = D_ALL; + xlog(fac, "writing fd to kernel failed: errno %d (%m)", + errno); + rc = errno; + goto error; + } + close(fd); + close(sockfd); + sockfd = fd = -1; + addr = addr->ai_next; } - close(fd); +error: + if (fd >= 0) + close(fd); + if (sockfd >= 0) + close(sockfd); + if (addrhead) + freeaddrinfo(addrhead); + return rc; +} - return; +int +nfssvc_set_sockets(const int family, const unsigned int protobits, + const char *host, const char *port) +{ + struct addrinfo hints = { .ai_flags = AI_PASSIVE }; + + hints.ai_family = family; + + if (!NFSCTL_ANYPROTO(protobits)) + return EPROTOTYPE; + else if (!NFSCTL_UDPISSET(protobits)) + hints.ai_protocol = IPPROTO_TCP; + else if (!NFSCTL_TCPISSET(protobits)) + hints.ai_protocol = IPPROTO_UDP; + + return nfssvc_setfds(&hints, host, port); } -static void -nfssvc_versbits(unsigned int ctlbits, int minorvers4) + +void +nfssvc_setvers(unsigned int ctlbits, int minorvers41) { int fd, n, off; - char buf[BUFSIZ], *ptr; + char *ptr; ptr = buf; off = 0; @@ -126,59 +280,57 @@ nfssvc_versbits(unsigned int ctlbits, int minorvers4) if (fd < 0) return; + if (minorvers41) + off += snprintf(ptr+off, sizeof(buf) - off, "%c4.1", + minorvers41 > 0 ? '+' : '-'); for (n = NFSD_MINVERS; n <= NFSD_MAXVERS; n++) { if (NFSCTL_VERISSET(ctlbits, n)) - off += snprintf(ptr+off, BUFSIZ - off, "+%d ", n); + off += snprintf(ptr+off, sizeof(buf) - off, "+%d ", n); else - off += snprintf(ptr+off, BUFSIZ - off, "-%d ", n); + off += snprintf(ptr+off, sizeof(buf) - off, "-%d ", n); } - n = minorvers4 >= 0 ? minorvers4 : -minorvers4; - if (n >= NFSD_MINMINORVERS4 && n <= NFSD_MAXMINORVERS4) - off += snprintf(ptr+off, BUFSIZ - off, "%c4.%d", - minorvers4 > 0 ? '+' : '-', - n); xlog(D_GENERAL, "Writing version string to kernel: %s", buf); - snprintf(ptr+off, BUFSIZ - off, "\n"); - if (write(fd, buf, strlen(buf)) != strlen(buf)) + snprintf(ptr+off, sizeof(buf) - off, "\n"); + if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf)) xlog(L_ERROR, "Setting version failed: errno %d (%m)", errno); close(fd); return; } + int -nfssvc(int port, int nrservs, unsigned int versbits, int minorvers4, - unsigned protobits, char *haddr) +nfssvc_threads(unsigned short port, const int nrservs) { struct nfsctl_arg arg; + struct servent *ent; + ssize_t n; int fd; - /* Note: must set versions before fds so that - * the ports get registered with portmap against correct - * versions - */ - nfssvc_versbits(versbits, minorvers4); - nfssvc_setfds(port, protobits, haddr); - fd = open(NFSD_THREAD_FILE, O_WRONLY); if (fd < 0) fd = open("/proc/fs/nfs/threads", O_WRONLY); if (fd >= 0) { /* 2.5+ kernel with nfsd filesystem mounted. - * Just write the number in. - * Cannot handle port number yet, but does anyone care? + * Just write the number of threads. */ - char buf[20]; - int n; - snprintf(buf, 20,"%d\n", nrservs); + snprintf(buf, sizeof(buf), "%d\n", nrservs); n = write(fd, buf, strlen(buf)); close(fd); - if (n != strlen(buf)) + if (n != (ssize_t)strlen(buf)) return -1; else return 0; } + if (!port) { + ent = getservbyname("nfs", "udp"); + if (ent != NULL) + port = ntohs(ent->s_port); + else + port = NFS_PORT; + } + arg.ca_version = NFSCTL_VERSION; arg.ca_svc.svc_nthreads = nrservs; arg.ca_svc.svc_port = port;