/* * mount_cdda, a Red Book filesystem server. * * Cannibalized from mount_iso9660 * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cdda.h" #include #include "main.h" #include "remote.h" #define MNTOPT_DEBUG "debug" #define MNTOPT_CACHE "cache" #define MNTOPT_NOTRANSLATE "notranslate" #define MNTOPT_SETX "setx" #define MNTOPT_NOEXT "noext" #define MNTOPT_NMCONV "nmconv" /* for ABI compliance; only needed when invoked as mount_cdfs, but take for all cases; the MIPS ABI Black Book 1.2 specifies system defaults vary, and defines 3 options: =c: no conversion (==notranslate), =l: convert to lowercase (the default for us), and =m: don't show version number (the default for us). */ #define MNTOPT_SUSP "susp" /* ABI noop */ #define MNTOPT_NOSUSP "nosusp" /* ABI synonym for noext */ #define MNTOPT_RRIP "rrip" /* ABI noop */ #define MNTOPT_NORRIP "norrip" /* ABI synonym for next */ extern void nfs_service(); /* extern CDPLAYER *cd; */ struct timeval timeout; uid_t uid; /* real user ID */ gid_t gid; /* real group ID */ int debug = 1; /* debug flag */ char *progname; static FILE *tracefile; static char *rootdir; /* shared between main and terminate */ /* static int print_cache_statistics; */ enum mountstat { UNMOUNTED, MOUNTED_BUT_DOWN, MOUNTED_AND_UP }; void usage(); static char *fullpath(char *pathname); static int makedir(char *); static enum mountstat checkmount(char *, int *); static char *type, *fsname; static void selfmount(struct mntent *, fhandle_t *, u_short, pid_t, enum mountstat); static void perrorf(char *, ...); void terminate(int status); static int nopt(struct mntent *mnt, char *opt); static char * str_opt(struct mntent *mnt, char *opt); /* * main(int argc, char *argv[]) * * Description: * Mount a CDDA file system, and act as an NFS server for that * system. * * This program is intended to only be used by mount(1M). When * mount encounters a file system type other than efs, nfs, or debug, * it executes mount_%s, %s being replaced by the file system type. * mount_cdda, therefore, is invoked by mount(1M) when it * encounters a file system type of cdda. * * Usage: * mount_cdda fsname dir type opts * fsname Must be /dev/scsi/* * dir The mount point * type Should be "cdda" * opts Supported options (in addition to standard * mount(1M) options: * debug - Program is not detatched from * the terminal * cache - Numeric option; specify the number * of blocks to be cached by the server */ main(int argc, char *argv[]) { int opt, sock, error; enum mountstat mstat; fhandle_t rootfh; SVCXPRT *transp; pid_t pid; fd_set readfds; extern char *optarg; extern int opterr, optind, optopt; /* int cache_blocks = 128; */ int cache_blocks = 0; char *pc, *cdfsopt=NULL; struct mntent mnt; /* * Initialize. */ uid = getuid(); gid = getgid(); progname = argv[0]; /* * Check for proper invocation via mount(1M). */ if (argc < 4) { fprintf(stderr, "usage: %s fsname dir type opts\n", progname); exit(2); } fsname = mnt.mnt_fsname = argv[1]; mnt.mnt_dir = argv[2]; type = mnt.mnt_type = argv[3]; if (argc == 5) mnt.mnt_opts = argv[4]; else mnt.mnt_opts = "dummyopt"; /* * Check mount options for debug and cache size */ if (hasmntopt(&mnt, MNTOPT_DEBUG)) { debug = 1; } if (hasmntopt(&mnt, MNTOPT_CACHE)) { cache_blocks = nopt(&mnt, MNTOPT_CACHE); } if (hasmntopt(&mnt, MNTOPT_NOTRANSLATE)) { cdda_disable_name_translations(); } if (hasmntopt(&mnt, MNTOPT_SETX)) { cdda_setx(); } if(cdfsopt=str_opt(&mnt, MNTOPT_NMCONV)) { if(*cdfsopt && !cdfsopt[1] && index("clm", *cdfsopt)) { if(*cdfsopt == 'c') cdda_disable_name_translations(); } else (void) fprintf(stderr, "%s: bad string option '%s=%s'\n", progname, MNTOPT_NMCONV, cdfsopt ); } rootdir = mnt.mnt_dir = fullpath(mnt.mnt_dir); /* * Do the NFS mount if file system is on another machine */ /* * We should get rid of this eventually, but this will allow * us to mount CDs from machines with old OS releases */ pc = strchr(mnt.mnt_fsname, ':'); if (pc) { remote_mount(&mnt); exit (0); } /* * Set up mount point and check whether it's already mounted and up. */ sock = RPC_ANYSOCK; mstat = checkmount(mnt.mnt_fsname, &sock); if (mstat == MOUNTED_AND_UP) { fprintf(stderr, "%s: %s is already mounted.\n", progname, rootdir); exit(1); } if (mstat == UNMOUNTED && makedir(rootdir) < 0) { perrorf("can't create %s", rootdir); exit(1); } /* * Set up Red Book filesystem and root file handle. */ error = cdda_openfs(mnt.mnt_fsname, rootdir, &rootfh); if (error) { errno = error; perrorf("can't initialize %s", mnt.mnt_fsname); exit(1); } /* * Create a service transport and register it. */ pmap_set(NFS_PROGRAM, NFS_VERSION, IPPROTO_UDP); transp = svcudp_create(sock); if (transp == NULL) { fprintf(stderr, "%s: cannot create udp service.\n", progname); exit(1); } if (!svc_register(transp, NFS_PROGRAM, NFS_VERSION, nfs_service, 0)) { fprintf(stderr, "%s: can't register myself as an NFS server.\n", progname); exit(1); } pmap_set(SGI_NFS_PROGRAM, SGI_NFS_VERSION, IPPROTO_UDP); if (!svc_register(transp, SGI_NFS_PROGRAM, SGI_NFS_VERSION, nfs_service, 0)) { fprintf(stderr, "%s: can't register myself as an SGI NFS server.\n", progname); } opt = 50000; setsockopt(transp->xp_sock, SOL_SOCKET, SO_RCVBUF, &opt, sizeof opt); setsockopt(transp->xp_sock, SOL_SOCKET, SO_SNDBUF, &opt, sizeof opt); /* svcudp_enablecache(transp, 1); */ /* svcudp_enablecache(transp, 64); */ /* * Setup fatal signal handlers before mounting. */ sigset(SIGHUP, terminate); sigset(SIGINT, terminate); sigset(SIGTERM, terminate); /* * Fork a child to run as an NFS server. The parent mounts the child * server on rootdir, then exits. */ #ifdef PROFILE pid = fork(); if (pid < 0) { perrorf("can't fork"); exit(1); } if (pid == 0) { selfmount(&mnt, &rootfh, transp->xp_port, getppid(), mstat); exit(0); } pid = getpid(); #else pid = fork(); if (pid < 0) { perrorf("can't fork"); exit(1); } if (pid > 0) { selfmount(&mnt, &rootfh, transp->xp_port, pid, mstat); exit(0); } #endif /* * Put the child in the background unless debugging. */ if (debug) { openlog(progname, LOG_PID|LOG_PERROR, LOG_DAEMON); } else { int fd = open("/dev/tty", 0); if (fd >= 0) ioctl(fd, TIOCNOTTY); for (fd = getdtablesize(); --fd >= 0; ) { if (fd == transp->xp_sock || cdda_isfd(fd)) continue; if (tracefile && fd == fileno(tracefile)) continue; close(fd); } openlog(progname, LOG_PID, LOG_DAEMON); (void)setsid(); (void)chdir("/"); } /* Set up a generous timeout */ timeout.tv_sec = 2; /* Serve incoming requests. */ for (;;) { readfds = svc_fdset; switch (select(transp->xp_sock+1, &readfds, NULL, NULL, &timeout)) { case -1: if (errno == EINTR) continue; syslog(LOG_ERR, "select: %m"); terminate(1); case 0: if (cd) { CDclose(cd); cd = NULL; } break; default: svc_getreqset(&readfds); } } /* NOTREACHED */ } static char * fullpath(char *pathname) { static char buf[PATH_MAX]; if (pathname[0] != '/') { if (getwd(buf) == NULL) { perrorf(buf); exit(1); } sprintf(buf + strlen(buf), "/%s", pathname); pathname = buf; } return pathname; } static int makedir(char *path) { struct stat sb; char *slash; int result; if (stat(path, &sb) == 0) { if (S_ISDIR(sb.st_mode)) return 0; errno = ENOTDIR; return -1; } if (errno != ENOENT) return -1; slash = strrchr(path, '/'); if (slash && slash != path) { *slash = '\0'; result = makedir(path); *slash = '/'; if (result < 0) return result; } return mkdir(path, 0777); } static char mounted[] = "/etc/gfstab"; static char lockfile[] = "/etc/.gfslock"; static enum mountstat checkmount(char *dir, int *sock) { FILE *mtab; enum mountstat mstat; long pos, nextpos; struct mntent *mnt; char *opt; pid_t pid; struct sockaddr_in sin; mstat = UNMOUNTED; mtab = setmntent(mounted, "r+"); if (mtab == NULL) return mstat; for (pos = 0; (mnt = getmntent(mtab)) != NULL; pos = nextpos) { nextpos = ftell(mtab); if (strcmp(mnt->mnt_dir, dir) == 0) { if ((opt = hasmntopt(mnt, "pid")) == NULL || sscanf(opt+3, "=%hu", &pid) != 1 || (opt = hasmntopt(mnt, "port")) == NULL || sscanf(opt+4, "=%hu", &sin.sin_port) != 1) { fseek(mtab, pos, 0); putc('#', mtab); fseek(mtab, nextpos, 0); continue; } sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); sin.sin_port = htons(sin.sin_port); bzero(sin.sin_zero, sizeof sin.sin_zero); *sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); if (*sock < 0) { perrorf("can't create socket"); exit(1); } if (bind(*sock, &sin, sizeof sin) == 0 || kill(pid, 0) < 0 && errno == ESRCH) { fseek(mtab, pos, 0); putc('#', mtab); mstat = MOUNTED_BUT_DOWN; } else { if (errno != EADDRINUSE) { perrorf("can't bind UDP port %u", ntohs(sin.sin_port)); exit(1); } mstat = MOUNTED_AND_UP; } break; } } endmntent(mtab); return mstat; } static void selfmount(struct mntent *mntp, fhandle_t *fh, u_short port, pid_t pid, enum mountstat mstat) { struct sockaddr_in sin; struct nfs_args args; char opts[MNTMAXSTR]; int mflags, optlen, type; FILE *mtab; sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); sin.sin_port = htons(port); bzero(sin.sin_zero, sizeof sin.sin_zero); args.addr = &sin; args.fh = fh; /* args.flags = NFSMNT_HOSTNAME|NFSMNT_TIMEO|NFSMNT_LOOPBACK|NFSMNT_RSIZE; */ args.flags = NFSMNT_HOSTNAME|NFSMNT_TIMEO|NFSMNT_RSIZE; args.rsize = MAX_NFS_BUFFER_SIZE; args.timeo = 100; args.hostname = mntp->mnt_fsname; mflags = MS_FSS|MS_DATA|MS_RDONLY; optlen = sprintf(opts, "%s,timeo=%d,port=%u,pid=%u,%s", mntp->mnt_opts, (int)args.timeo, port, pid, MNTOPT_RO); /* * Make mounts soft by default; it's too easy to eject a disc */ if (!hasmntopt(mntp, MNTOPT_HARD)) { args.flags |= NFSMNT_SOFT; optlen += sprintf(&opts[optlen], ",%s", MNTOPT_SOFT); } type = sysfs(GETFSIND, FSID_NFS); if (type < 0) { fprintf(stderr, "%s: NFS is not installed.\n", progname); exit(1); } if (mount(mntp->mnt_fsname, mntp->mnt_dir, mflags, type, &args, sizeof args) < 0 && !(mstat == MOUNTED_BUT_DOWN && errno == EBUSY)) { perrorf("can't mount myself"); kill(pid, SIGKILL); exit(1); } mntp->mnt_opts = opts; mntp->mnt_freq = mntp->mnt_passno = 0; if (access(mounted, F_OK) < 0) { int fd; fd = creat(mounted, 0644); close(fd); } mtab = setmntent(mounted, "r+"); if (mtab == NULL || addmntent(mtab, mntp) != 0) { perrorf(mounted); kill(pid, SIGKILL); terminate(1); } endmntent(mtab); } static void perrorf(char *format, ...) { unsigned error; va_list ap; error = errno; fprintf(stderr, "%s: ", progname); va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); if (error < sys_nerr) fprintf(stderr, ": %s", sys_errlist[error]); fputs(".\n", stderr); } void trace(char *format, ...) { va_list ap; if (tracefile == NULL) return; va_start(ap, format); vfprintf(tracefile, format, ap); va_end(ap); } void panic(char *format, ...) { va_list ap; va_start(ap, format); vsyslog(LOG_ALERT, format, ap); va_end(ap); terminate(-1); } /* extern int cache_hits; */ /* extern int cache_misses; */ void terminate(int status) { FILE *mtab, *tmtab; int tmpfd; struct mntent *mnt; static char tmpname[] = "/etc/mountcddaXXXXXX"; int lockfd; if (status < 0) abort(); /* * This cleans up wrt devscsi */ cdda_removefs(); /* * This lock file eliminates race conditions that can occur * with multiple CD-ROM drives or with floppy drives (mountdos * also use /etc/gfstab). If two processes execute the code * below at the same time, /etc/gfstab can get hosed. */ lockfd = open(lockfile, O_RDWR | O_CREAT, 0644); if (lockfd != -1) { lockf(lockfd, F_LOCK, 0); } /* * Remove our gfstab entry */ if ((mtab = setmntent(mounted, "r+")) == NULL || (tmpfd = mkstemp(tmpname)) < 0 || (tmtab = fdopen(tmpfd, "w")) == NULL) { syslog(LOG_ERR, "can't open %s: %m", mtab ? tmpname : mounted); endmntent(mtab); if (lockfd != -1) { lockf(lockfd, F_ULOCK, 0); } exit(1); } while ((mnt = getmntent(mtab)) != NULL) { if (strcmp(mnt->mnt_dir, rootdir) == 0 && strcmp(mnt->mnt_fsname, fsname) == 0 && strcmp(mnt->mnt_type, type) == 0) continue; fprintf(tmtab, "%s %s %s %s %d %d\n", mnt->mnt_fsname, mnt->mnt_dir, mnt->mnt_type, mnt->mnt_opts, mnt->mnt_freq, mnt->mnt_passno); } if (fchmod(tmpfd, 0644) < 0 || fclose(tmtab) == EOF || rename(tmpname, mounted) < 0) { syslog(LOG_ERR, "can't update %s: %m", mounted); (void) unlink(tmpname); endmntent(mtab); if (lockfd != -1) { lockf(lockfd, F_ULOCK, 0); } exit(1); } endmntent(mtab); if (lockfd != -1) { lockf(lockfd, F_ULOCK, 0); } exit(0); } /* * Return the value of a numeric option of the form foo=x, if * option is not found or is malformed, return 0. */ static int nopt(struct mntent *mnt, char *opt) { int val = 0; char *equal; char *str; if (str = hasmntopt(mnt, opt)) { if (equal = index(str, '=')) { val = atoi(&equal[1]); } else { (void) fprintf(stderr, "%s: bad numeric option '%s'\n", progname, str ); } } return (val); } /* * Return the value of a string option of the form foo=x, if * option is not found or is malformed, return NULL. */ static char * str_opt(struct mntent *mnt, char *opt) { char *equal, *index(); char *str; if (str = hasmntopt(mnt, opt)) { if((equal = index(str, '=')) && equal[1]) { return &equal[1]; } else { (void) fprintf(stderr, "%s: bad string option '%s'\n", progname, str ); } } return NULL; }