1
0
Files
irix-657m-src/eoe/cmd/mediad/main.C
2022-09-29 17:59:04 +03:00

1038 lines
22 KiB
C

#include <assert.h>
#include <bstring.h>
#include <ctype.h>
#include <dslib.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <syslog.h>
#include <unistd.h>
#include "CompatClient.H"
#include "CompatListener.H"
#include "Device.H"
#include "DeviceAddress.H"
#include "DeviceDSO.H"
#include "DeviceInfo.H"
#include "DeviceMonitor.H"
#include "DSReq.H"
#include "Fsd.H"
#include "Inventory.H"
#include "Log.H"
#include "MediaDaemon.H"
#include "MonitorClient.H"
#include "PartitionAddress.H"
#include "Scheduler.H"
#include "RPCListener.H"
#include "VolumeAddress.H"
#include "Volume.H" // XXX Hack for security bug late in
// IRIX 6.5 release cyle.
extern int dsdebug; // when non-zero enables dslib debug messages
static int mediad_main(int argc, char *argv[]);
static int eject_main(int argc, char *argv[]);
//static int mediad_inetd_main(int argc, char *argv[]);
static void mediad_usage();
static void eject_usage();
//static void inetd_usage();
//static void do_default_reply(int loglevel);
static void start_daemon(bool started_by_inetd, bool debugging, int loglevel);
static int do_eject(const char *path, int ctlr, int id, int lun);
static int do_eject_no_mediad(const char *path, int ctlr, int id, int lun);
static int execute(const char *argv[3]);
static void do_kill();
static void do_show_mpoint(const char *fsname);
static void do_add_dev(const char *fsname,
const char *dir,
const char *mntopts,
bool immediate);
static void do_query_dev(const char *fsname);
static void do_remove_dev(const char *fsname, bool immediate);
static void do_set_log_level(int loglevel);
#ifdef MEDIAD_FROM_INETD
static bool sigusr1_seen;
#endif
// Utility functions. Simple string handling functions, simple
// system call wrappers.
// isnumber() returns true if str is an unsigned decimal number.
static bool
isnumber(const char *str)
{
for ( ; *str; str++)
if (!isdigit(*str))
return false;
return true;
}
// basename() returns the last component of a pathname. Note
// that the component might have trailing '/' characters.
static const char *
basename(const char *path)
{
const char *base = path;
for (const char *p = path; *p; p++)
if (*p == '/' && p[1] != '\0' && p[1] != '/')
base = p + 1;
return base;
}
// suser() returns true if program was invoked by the superuser.
static bool
suser()
{
return getuid() == 0; // Check that real uid == 0.
}
#ifdef MEDIAD_FROM_INETD
// issock() -- Function ripped off from rpc.mountd.c. Returns true if
// file descriptor is a socket.
static bool
issock(int fd)
{
struct stat st;
if (fstat(fd, &st) < 0)
return false;
// SunOS returns S_IFIFO for sockets, while 4.3 returns 0 and does not
// even have an S_IFIFO mode. Since there is confusion about what the
// mode is, we check for what it is not instead of what it is.
switch (st.st_mode & S_IFMT)
{
case S_IFCHR:
case S_IFREG:
case S_IFLNK:
case S_IFDIR:
case S_IFBLK:
return false;
default:
return true;
}
}
#endif
// Send a test message to mediad and return true if successful.
static bool
test_mediad_present()
{
CompatClient cc;
cc.verbose(false);
cc.send_test();
return cc.status() == RMED_NOERROR;
}
// mediad is invoked three ways --
//
// as eject
// from inetd
// from a shell
//
// The decision process is --
// if argv[0] is "eject" or ".../eject", then this is eject.
// else if stdin is a socket, then we were invoked from inetd,
// else we were invoked from a shell.
extern int
main(int argc, char *argv[])
{
/* Security Bug 530697 - watch for missing argv */
if (argc == 0 || argv[0] == NULL)
{
fprintf(stderr, "mediad: no argv\n");
exit(1);
}
Log::foreground(); // Log to stderr, not syslog.
if (!strncmp(basename(argv[0]), "eject", 5))
{
// This is eject.
Log::name("eject"); // This program is named eject.
return eject_main(argc, argv);
}
#ifdef MEDIAD_FROM_INETD
else if (issock(0))
{
// This is mediad invoked from inetd.
Log::name("mediad"); // This program is named mediad.
return mediad_inetd_main(argc, argv);
}
#endif
else
{
// This is mediad.
Log::name("mediad"); // This program is named mediad.
return mediad_main(argc, argv);
}
}
// Argument processing when mediad is invoked from a shell.
//
// Verbs: a e k m p q r u (default: a)
// Adverbs: f modifies/implies a
// i modifies p, r
// o modifies p
// Confused: l is either a verb or an adverb modifying a.
static int
mediad_main(int argc, char *argv[])
{
enum action {
START_DAEMON,
EJECT,
KILL,
SET_LOG_LEVEL,
SHOW_MPOINT,
ADD_DEV,
QUERY_DEV,
REMOVE_DEV
};
enum modifier {
FOREGROUND = 1 << 0,
IMMEDIATE = 1 << 1,
LOGLEVEL = 1 << 2,
OPTIONS = 1 << 3,
TYPE = 1 << 4
};
int nverbs = 0;
int modifiers = 0;
enum action what_to_do = START_DAEMON;
int ok_modifiers = FOREGROUND | LOGLEVEL;
int loglevel = -1;
bool any_user_ok = false;
bool debugging = false;
char *fsname = NULL;
char *dir = NULL;
#ifdef OBSOLETE
char *type = NULL;
#endif
char *mntopts = NULL;
int eject_ctlr = -1, eject_id = -1, eject_lun = -1;
bool immediate = false;
// Argument processing. Each option flag is either a verb or a
// modifier. There can only be one verb, which is kept track of
// in what_to_do. (Zero verbs are okay and imply "-a"). The
// flag word "modifiers" keeps track of which modifiers have been
// seen. The word "ok_modifiers" keeps track of which modifiers
// are okay for the selected verb.
//
// Some flags eat arguments. Those that eat one arg do it
// in the normal way by using getopt. "-p" takes two
// args, though, and "-e" will take one, two or three args.
for (int opt; (opt = getopt(argc, argv, "ade:fikl:m:no:pqr:t:u")) != -1; )
switch(opt)
{
case 'a': // mediad [-l N] -a
nverbs++;
what_to_do = START_DAEMON;
ok_modifiers = FOREGROUND | LOGLEVEL;
break;
case 'd':
dsdebug = 1;
break;
case 'e': // mediad [-l N] -e [ctlr] id [lun]
// mediad [-l N] -e device|mountpt
nverbs++;
what_to_do = EJECT;
ok_modifiers = LOGLEVEL;
any_user_ok = true;
if (isnumber(optarg))
{ eject_ctlr = 0;
eject_id = 0;
eject_lun = 0;
if (argv[optind] && isnumber(argv[optind]))
{ // Two numeric arguments
eject_ctlr = atoi(optarg);
eject_id = atoi(argv[optind++]);
if (argv[optind] && isnumber(argv[optind]))
{ // Three numeric arguments
eject_lun = atoi(argv[optind++]);
}
}
else
{ // One numeric argument
eject_id = atoi(optarg);
}
}
else
{
// One path argument.
fsname = optarg;
}
break;
case 'f': // mediad -f
// mediad -f -a
modifiers |= FOREGROUND;
debugging = true;
break;
#ifdef OBSOLETE
case 'i': // mediad -i -p dev dir
// mediad -i -r dev
modifiers |= IMMEDIATE;
immediate = true;
break;
#endif
case 'k': // mediad -k
nverbs++;
what_to_do = KILL;
ok_modifiers = 0;
break;
case 'l': // mediad -l N
// mediad -l N -a
loglevel = atoi(optarg);
// verb/adverb determination is made below.
break;
case 'm': // mediad -m device
nverbs++;
what_to_do = SHOW_MPOINT;
ok_modifiers = 0;
any_user_ok = true;
fsname = optarg;
break;
case 'n':
// XXX Security fix late in IRIX 6.5 release.
Volume::setGlobalOptions("nosuid");
break;
#ifdef OBSOLETE
case 'o': // mediad -o options -p ...
modifiers |= OPTIONS;
mntopts = optarg;
break;
case 'p': // mediad [-i] [-o opts] -p dev dir
nverbs++;
what_to_do = ADD_DEV;
ok_modifiers = IMMEDIATE | OPTIONS;
if (optind != argc - 2)
mediad_usage();
fsname = argv[optind++];
dir = argv[optind++];
break;
#endif
case 'q': // mediad -q device
nverbs++;
what_to_do = QUERY_DEV;
ok_modifiers = 0;
fsname = argv[optind++];
break;
#ifdef OBSOLETE
case 'r': // mediad [-i] -r device
nverbs++;
what_to_do = REMOVE_DEV;
ok_modifiers = IMMEDIATE;
fsname = optarg;
break;
case 't':
modifiers |= TYPE;
type = optarg;
break;
#endif
case 'u': // mediad [-l N] -u (eject first device)
nverbs++;
what_to_do = EJECT;
ok_modifiers = LOGLEVEL;
any_user_ok = true;
break;
default:
mediad_usage();
}
// loglevel hack. "mediad -l N" means send a message to mediad
// setting its log level to N. "mediad -l N -a" means start
// mediad with log level N.
if (loglevel != -1)
{ if (nverbs == 0)
{ what_to_do = SET_LOG_LEVEL;
nverbs++;
ok_modifiers = 0;
}
else
modifiers |= LOGLEVEL;
}
// Sanity checks. Crash and burn if two verbs specified or if
// modifier that doesn't work with this verb specified or if
// extra arguments specified.
if (nverbs > 1)
mediad_usage();
if (modifiers & ~ok_modifiers)
mediad_usage();
if (optind < argc)
mediad_usage();
// Check whether this user can do that.
if (!any_user_ok && !suser())
{ Log::error("must be superuser");
return 1;
}
// Don't allow -d without -f.
if (dsdebug && !debugging)
mediad_usage();
switch (what_to_do)
{
case START_DAEMON:
start_daemon(false, debugging, loglevel);
break;
case EJECT:
do_eject(fsname, eject_ctlr, eject_id, eject_lun);
break;
case KILL:
do_kill();
break;
case SHOW_MPOINT:
do_show_mpoint(fsname);
break;
case ADD_DEV:
do_add_dev(fsname, dir, mntopts, immediate);
break;
case QUERY_DEV:
do_query_dev(fsname);
break;
case REMOVE_DEV:
do_remove_dev(fsname, immediate);
break;
case SET_LOG_LEVEL:
do_set_log_level(loglevel);
break;
default:
assert(0); // Can't get here.
}
return 0;
}
// Eject argument parsing
static int
eject_main(int argc, char *argv[])
{
int ctlr = -1, id = -1, lun = -1;
char *fsname = NULL;
switch (argc)
{
case 1: // eject
break;
case 2: // eject fsname
// eject id
if (isnumber(argv[1]))
{ ctlr = 0;
id = atoi(argv[1]);
lun = 0;
}
else
fsname = argv[1];
break;
case 3: // eject ctlr id
if (!isnumber(argv[1]) || !isnumber(argv[2]))
eject_usage();
ctlr = atoi(argv[1]);
id = atoi(argv[2]);
lun = 0;
break;
case 4: // eject ctlr id lun
if (!isnumber(argv[1]) || !isnumber(argv[2]) || !isnumber(argv[3]))
eject_usage();
ctlr = atoi(argv[1]);
id = atoi(argv[2]);
lun = atoi(argv[3]);
break;
default:
eject_usage();
}
return do_eject(fsname, ctlr, id, lun);
}
#ifdef MEDIAD_FROM_INETD
// argument processing when mediad is invoked from inetd.
// Only -a and -l N are allowed.
static int
mediad_inetd_main(int argc, char *argv[])
{
Log::background();
if (!suser())
{ Log::error("must be launched by superuser");
return 1;
}
// Argument checking
int loglevel = -1;
for (int opt; (opt = getopt(argc, argv, "al:")) != -1; )
switch (opt)
{
case 'a':
// Ignore -a.
break;
case 'l':
// Set loglevel
if (isnumber(optarg))
loglevel = atoi(optarg);
inetd_usage();
default:
assert(0); // Can't get here.
}
if (optind != argc)
inetd_usage();
do_default_reply(loglevel);
return 0;
}
static void
inetd_usage()
{
static bool been_here = false;
if (!been_here)
{
been_here = true;
Log::error("use: mediad [-a] [-l loglevel]");
}
// inetd usage messages are nonfatal, because the user may not look
// in SYSLOG for a long time. Just return.
}
#endif /* MEDIAD_FROM_INETD */
static void
eject_usage()
{
fprintf(stderr, "use: eject\n");
fprintf(stderr, " eject device_name\n");
fprintf(stderr, " eject mount_point\n");
fprintf(stderr, " eject scsi_id (numeric)\n");
fprintf(stderr, " eject scsi_ctlr scsi_id [scsi_lun] (numeric)\n");
fprintf(stderr, "\n");
exit(1);
}
static void
mediad_usage()
{
fprintf(stderr, "Usage:\n");
fprintf(stderr, " mediad\n");
fprintf(stderr, " mediad [-l level] [ -n ] "
"-e [controller_id] scsi_id [lun] | dir | device\n");
#ifdef OBSOLETE
fprintf(stderr, " mediad [-i] [-o options] -p device dir\n");
fprintf(stderr, " mediad [-i] -r device\n");
#endif
fprintf(stderr, " mediad -m dir\n");
fprintf(stderr, " mediad -q device\n");
fprintf(stderr, " mediad [-l loglevel] [-f] -a\n");
fprintf(stderr, " mediad -k\n");
fprintf(stderr, " mediad [-l level] -u\n");
fprintf(stderr, " mediad [-d] -f\n");
fprintf(stderr, "\n");
exit(1);
}
//////////////////////////////////////////////////////////////////////////////
// Actions. main() or *_main() calls one of these routines after parsing
// argv.
#ifdef MEDIAD_FROM_INETD
static void
sigusr1_handler()
{
sigusr1_seen = true;
}
static void
sigalrm_handler()
{ }
static void
do_default_reply(int loglevel)
{
const int listenfd = 0; // standard input; passed by inetd.
int clientfd = accept(listenfd, (sockaddr *) &addr, sizeof addr);
if (clientfd < 0)
{ Log::perror("accept failed");
exit(1);
}
// Set up to receive SIGALRM and SIGUSR1.
sigusr1_seen = false;
signal(SIGALRM, (SIG_PF) sigalrm_handler);
signal(SIGUSR1, (SIG_PF) sigusr1_handler);
// Create a MonitorClient which will immediately send a
// MCE_NO_DEVICES message, then destroy the MonitorClient,
// closing the socket. (The curly brackets force the
// MonitorClient out of scope immediately, so it is destroyed.)
XXX this will not work any more because MonitorClient constructor args changed.
{ MonitorClient mc(clientfd, true); }
// Wait two seconds for a SIGUSR1 signal.
// XXX this should run the scheduler and accept/flush other clients.
alarm(2);
sigpause(0);
alarm(0);
// If we got SIGUSR1, start the daemon, otherwise return/exit.
if (sigusr1_seen)
start_daemon(true, false, loglevel);
}
#endif /* MEDIAD_FROM_INETD */
static void
signal_handler()
{
Scheduler::exit();
}
static void
start_daemon(bool started_by_inetd, bool debugging, int loglevel)
{
if (test_mediad_present())
{ Log::error("another mediad is already running.");
exit(1);
}
if (debugging)
Log::debug();
else
{ _daemonize(0, -1, -1, -1);
Log::background();
}
if (loglevel != -1)
{ if (loglevel >= LOG_DEBUG)
Log::debug();
else if (loglevel >= LOG_INFO)
Log::info();
else
Log::error();
}
// Catch common signals.
signal(SIGHUP, (SIG_PF) signal_handler);
signal(SIGINT, (SIG_PF) signal_handler);
signal(SIGTERM, (SIG_PF) signal_handler);
signal(SIGPIPE, SIG_IGN);
// Instantiate a media daemon.
MediaDaemon cosmo_mediad; // Everything should be named Cosmo.
if (Device::count() == 0)
{ Log::info("No devices to monitor. Exiting.");
return;
}
// Instantiate listeners.
RPCListener left_ear(true, started_by_inetd);
CompatListener right_ear(CompatListener::MEDIAD_SOCKNAME);
// The main loop
Scheduler::loop();
Log::debug("exiting");
}
static int
do_eject(const char *path, int ctlr, int id, int lun)
{
CompatClient cc;
cc.send_eject(path, ctlr, id, lun);
int rcode = cc.status();
if (rcode == RMED_ENOMEDIAD)
{
// Couldn't send message to mediad. Try to find and eject
// the device.
rcode = do_eject_no_mediad(path, ctlr, id, lun);
}
if (rcode != RMED_NOERROR)
Log::error(CompatClient::message(rcode));
return rcode;
}
static int
do_eject_no_mediad(const char *path, int ctlr, int id, int lun)
{
DeviceAddress da;
Inventory hinv;
if (path)
{
// Try to find device.
VolumeAddress va(path);
if (!va.valid())
{
// Not a device. Maybe it's a mount point.
Fsd mtab("/etc/mtab");
mntent *mnt = mtab.at_dir(path);
if (mnt)
va = VolumeAddress(mnt->mnt_fsname);
}
// Extract device address from volume address.
// XXX in future, should find all distinct device addresses
// and eject them all.
if (va.valid())
da = va.partition(0)->device();
}
else if (ctlr >= 0)
{
// eject by SCSI address
da = DeviceAddress(ctlr, id, lun);
}
else
{
// eject first find -- take the first recognizable entry in the
// H/W inventory.
const inventory_s *invp = hinv[0];
if (invp)
da = DeviceAddress(*invp);
}
if (!da.valid())
return RMED_ENODEVICE;
const inventory_s *invp = hinv.at(da);
if (!invp)
return RMED_ENODEVICE;
// Check for filesystems mounted on this device. Iterate through
// /etc/mtab and compare each filesystem's device to this device.
// Dismount any that match.
Fsd mtab("/etc/mtab");
const mntent *mnt;
for (int k = 0; mnt = mtab[k]; k++)
{
VolumeAddress mva(mnt->mnt_fsname);
if (mva.valid())
{
const PartitionAddress *mpa;
for (int j = 0; mpa = mva.partition(j); j++)
{
if (mpa->device() == da)
{
const char *umount_argv[3] = {
"/etc/umount", mnt->mnt_dir, NULL
};
int status = execute(umount_argv);
if (status != 0)
return RMED_ECANTUMOUNT;
break;
// Continue search with next mtab entry.
}
}
}
}
// We have a device and an inventory record. Probe for the device
// and eject it. This code duplicates MediaDaemon::start_monitor()
// and I should only have one copy of it.
DeviceInfo info(da, *invp);
SCSIAddress *sa = da.as_SCSIAddress();
if (sa)
{
char inquiry[98];
bzero(inquiry, sizeof inquiry);
DSReq dsp(*sa);
if (dsp && inquiry12(dsp, inquiry, sizeof inquiry, 0) == STA_GOOD)
info.inquiry(inquiry);
}
DeviceLibrary devlib("/usr/lib/devicelib");
DeviceDSO *dso;
for (unsigned i = 0; dso = devlib[i]; i++)
{ info.dso(dso);
Device *device = dso->instantiate(info);
int err = RMED_NOERROR;
if (device) {
err = device->eject();
dso->deinstantiate(device);
return err;
}
}
return RMED_ENODEVICE;
}
static int
execute(const char *argv[3])
{
int status = 0;
#define STR(p) ((p) ? (p) : "")
Log::debug("executing command %s %s",
STR(argv[0]), STR(argv[1]));
assert(argv[0][0] == '/');
int pid = fork();
if (pid < 0)
{ Log::perror("fork failed");
return -1;
}
if (pid == 0)
{
// Child
execv(argv[0], (char *const *) argv);
Log::perror("%s exec failed", argv[0]);
_exit(1);
}
else
{
// Parent
do
{
if (waitpid(pid, &status, NULL) != pid)
{ Log::perror("%s wait failed", argv[0]);
return -1;
}
} while (!WIFEXITED(status));
if (status != 0)
Log::debug("command failed with status %d", status);
}
return status;
}
static void
do_kill()
{
CompatClient cc;
cc.send_kill();
}
static void
do_show_mpoint(const char *fsname)
{
CompatClient cc;
cc.send_show_mpoint(fsname);
if (cc.status() == RMED_NOERROR)
printf("%s\n", cc.reply().mpoint);
else if (cc.status() == RMED_ENOMEDIAD)
{
// No mediad. We'll see if we can find anything in /etc/fsd.auto.
const Fsd& fsd_auto = Fsd::fsd_auto();
const mntent *mntp;
for (unsigned i = 0; mntp = fsd_auto[i]; i++)
if (!strcmp(fsname, mntp->mnt_fsname))
printf("%s\n", mntp->mnt_dir);
}
}
static void
do_query_dev(const char * /* fsname */ )
{
int write_me = 0; assert(write_me);
}
static char *
munge_options(const char *oldopts, const char *newopts, bool mon)
{
char optbuffer[MNTMAXSTR];
// Build the
if (newopts)
strcpy(optbuffer, newopts);
else if (oldopts)
{
mntent mnt = { NULL, NULL, NULL, (char *) oldopts, 0, 0 };
const char *oldmon = hasmntopt(&mnt, "mon");
if (oldmon)
{
if (oldmon != oldopts)
{ strcpy(optbuffer, oldopts);
optbuffer[oldmon - oldopts - 1] = '\0'; // truncate at comma
}
else
optbuffer[0] = '\0';
const char *p = strchr(oldmon, ',');
if (p)
strcat(optbuffer, p);
}
else
strcpy(optbuffer, oldopts);
}
else
optbuffer[0] = '\0';
// Append "mon=on/off".
if (optbuffer[0])
strcat(optbuffer, ",");
strcat(optbuffer, mon ? "mon=on" : "mon=off");
return optbuffer;
}
static void
do_add_dev(const char *fsname,
const char *dir,
const char *mntopts,
bool immediate)
{
Fsd& fsd_auto = Fsd::fsd_auto();
const mntent *mnt;
bool found = false;
for (unsigned i = 0; !found && (mnt = fsd_auto[i]); i++)
{ assert(mnt->mnt_fsname);
if (!strcmp(mnt->mnt_fsname, fsname) &&
!strcmp(mnt->mnt_type, "mediad"))
{ char *newopts = munge_options(mnt->mnt_opts, mntopts, true);
const mntent newmnt = {
(char *) fsname,
(char *) dir,
"mediad",
newopts, 0, 0
};
fsd_auto.replace(i, &newmnt);
found = true;
}
}
if (!found)
{ mntent newmnt = {
(char *) fsname,
(char *) dir,
"mediad",
"mon=on",
0, 0
};
fsd_auto.append(&newmnt);
}
// Inform mediad that it should monitor the new device.
if (immediate)
{ CompatClient cc;
cc.send_start_entry(fsname);
if (cc.status() == RMED_ENOMEDIAD)
{
int write_me = 0; assert(write_me);
// Start mediad.
}
}
}
static void
do_remove_dev(const char *fsname, bool immediate)
{
Fsd& fsd_auto = Fsd::fsd_auto();
const mntent *mnt;
bool found = false;
for (unsigned i = 0; !found && (mnt = fsd_auto[i]); i++)
{ assert(mnt->mnt_fsname);
if (!strcmp(mnt->mnt_fsname, fsname) &&
!strcmp(mnt->mnt_type, "mediad"))
{ char *newopts = munge_options(mnt->mnt_opts, NULL, false);
const mntent newmnt = {
(char *) fsname,
mnt->mnt_dir,
"mediad",
newopts, 0, 0
};
fsd_auto.replace(i, &newmnt);
found = true;
}
}
if (!found)
{ mntent newmnt = {
(char *) fsname,
".",
"mediad",
"mon=off",
0, 0
};
fsd_auto.append(&newmnt);
}
// Inform mediad that it should monitor the new device.
if (immediate)
{ CompatClient cc;
cc.send_stop_entry(fsname);
}
}
static void
do_set_log_level(int level)
{
CompatClient cc;
cc.send_set_log_level(level);
}