/* * utils/nfsd/nfssvc.c * * Run an NFS daemon. * * Copyright (C) 1995, 1996 Olaf Kirch */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "nfslib.h" #include "xlog.h" #include "nfssvc.h" #include "version.h" #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]; /* * 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. */ void nfssvc_mount_nfsdfs(char *progname) { int err; struct stat statbuf; err = stat(NFSD_THREAD_FILE, &statbuf); if (err == 0) return; if (errno != ENOENT) { xlog(L_ERROR, "Unable to stat %s: errno %d (%m)", NFSD_THREAD_FILE, errno); return; } /* * 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. */ err = 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, bounded = 0; struct addrinfo *addrhead = NULL, *addr; char *proto, *family; /* * if file can't be opened, fail. */ fd = open(NFSD_PORTS_FILE, O_WRONLY); if (fd < 0) return 0; 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: %s", node ? node : "ANYADDR", port, 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; } switch(addr->ai_addr->sa_family) { case AF_INET: family = "AF_INET"; break; #ifdef IPV6_SUPPORTED case AF_INET6: family = "AF_INET6"; break; #endif /* IPV6_SUPPORTED */ default: addr = addr->ai_next; continue; } /* 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_ERROR, "unable to create %s %s socket: " "errno %d (%m)", family, proto, errno); rc = errno; goto error; } addr = addr->ai_next; continue; } xlog(D_GENERAL, "Created %s %s socket.", family, proto); #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 (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); rc = errno; goto error; } if (fd < 0) fd = open(NFSD_PORTS_FILE, O_WRONLY); 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; } bounded++; close(fd); close(sockfd); sockfd = fd = -1; addr = addr->ai_next; } error: if (fd >= 0) close(fd); if (sockfd >= 0) close(sockfd); nfs_freeaddrinfo(addrhead); return (bounded ? 0 : rc); } int nfssvc_set_sockets(const unsigned int protobits, const char *host, const char *port) { struct addrinfo hints = { .ai_flags = AI_PASSIVE }; #ifdef IPV6_SUPPORTED hints.ai_family = AF_UNSPEC; #else /* IPV6_SUPPORTED */ hints.ai_family = AF_INET; #endif /* IPV6_SUPPORTED */ 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); } int nfssvc_set_rdmaport(const char *port) { struct servent *sv = getservbyname(port, "tcp"); int nport; char buf[20]; int ret; int fd; if (sv) nport = ntohs(sv->s_port); else { char *ep; nport = strtol(port, &ep, 10); if (!*port || *ep) { xlog(L_ERROR, "unable to interpret port name %s", port); return 1; } } fd = open(NFSD_PORTS_FILE, O_WRONLY); if (fd < 0) return 1; snprintf(buf, sizeof(buf), "rdma %d", nport); ret = 0; if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf)) { ret= errno; xlog(L_ERROR, "Unable to request RDMA services: %m"); } close(fd); return ret; } void nfssvc_set_time(const char *type, const int seconds) { char pathbuf[40]; char nbuf[10]; int fd; snprintf(pathbuf, sizeof(pathbuf), NFSD_FS_DIR "/nfsv4%stime", type); snprintf(nbuf, sizeof(nbuf), "%d", seconds); fd = open(pathbuf, O_WRONLY); if (fd >= 0) { if (write(fd, nbuf, strlen(nbuf)) != (ssize_t)strlen(nbuf)) xlog(L_ERROR, "Unable to set nfsv4%stime: %m", type); close(fd); } if (strcmp(type, "grace") == 0) { /* set same value for lockd */ fd = open("/proc/sys/fs/nfs/nlm_grace_period", O_WRONLY); if (fd >= 0) { if (write(fd, nbuf, strlen(nbuf)) != (ssize_t)strlen(nbuf)) xlog(L_ERROR, "Unable to write nlm_grace_period : %m"); close(fd); } } } void nfssvc_get_minormask(unsigned int *mask) { int fd; char *ptr = buf; ssize_t size; fd = open(NFSD_VERS_FILE, O_RDONLY); if (fd < 0) return; size = read(fd, buf, sizeof(buf)); if (size < 0) { xlog(L_ERROR, "Getting versions failed: errno %d (%m)", errno); goto out; } ptr[size] = '\0'; for (;;) { unsigned vers, minor = 0; char *token = strtok(ptr, " "); if (!token) break; ptr = NULL; if (*token != '+' && *token != '-') continue; if (sscanf(++token, "%u.%u", &vers, &minor) > 0 && vers == 4 && minor <= NFS4_MAXMINOR) NFSCTL_MINORSET(*mask, minor); } out: close(fd); return; } static int nfssvc_print_vers(char *ptr, unsigned size, unsigned vers, unsigned minorvers, int isset, int force4dot0) { char sign = isset ? '+' : '-'; if (minorvers == 0) if (linux_version_code() < MAKE_VERSION(4, 11, 0) || !force4dot0) return snprintf(ptr, size, "%c%u ", sign, vers); return snprintf(ptr, size, "%c%u.%u ", sign, vers, minorvers); } void nfssvc_setvers(unsigned int ctlbits, unsigned int minorvers, unsigned int minorversset, int force4dot0) { int fd, n, off; off = 0; fd = open(NFSD_VERS_FILE, O_WRONLY); if (fd < 0) return; for (n = NFSD_MINVERS; n <= ((NFSD_MAXVERS < 3) ? NFSD_MAXVERS : 3); n++) off += nfssvc_print_vers(&buf[off], sizeof(buf) - off, n, 0, NFSCTL_VERISSET(ctlbits, n), 0); for (n = 0; n <= NFS4_MAXMINOR; n++) { if (!NFSCTL_MINORISSET(minorversset, n)) continue; off += nfssvc_print_vers(&buf[off], sizeof(buf) - off, 4, n, NFSCTL_MINORISSET(minorvers, n), (n == 0) ? force4dot0 : 0); } if (!off--) goto out; buf[off] = '\0'; xlog(D_GENERAL, "Writing version string to kernel: %s", buf); snprintf(&buf[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); out: close(fd); return; } int nfssvc_threads(const int nrservs) { ssize_t n; int fd; 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 of threads. */ snprintf(buf, sizeof(buf), "%d\n", nrservs); n = write(fd, buf, strlen(buf)); close(fd); if (n != (ssize_t)strlen(buf)) return -1; else return 0; } return -1; }