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

923 lines
21 KiB
C

#include "Config.H"
#include <assert.h>
#include <ctype.h>
#include <fam.h>
#include <mntent.h>
#include <mediad.h> // for DEFAULT_*CHK_INTERVAL
#include <stdio.h>
#include <string.h>
#include "DeviceAddress.H"
#include "FAMonitor.H"
#include "Log.H"
#include "PartitionAddress.H"
#include "VolumeAddress.H"
#include "operators.H" // for config::operator !=
#include "strplus.H"
// the config class and its fu are defined local to this file instead
// of in the Config class because I don't want Config.H to include
// VolumeAddress.H or DeviceAddress.H.
class config {
public:
struct monitor_device {
DeviceAddress da;
int inschk;
int rmvchk;
};
struct mount_volume {
VolumeAddress va;
char *dir;
char *opts;
bool is_shared;
bool is_unshared;
};
struct share_device {
DeviceAddress da;
};
config();
~config();
// Accessors
bool device_is_ignored(const DeviceAddress&) const;
bool volume_is_ignored(const VolumeAddress&) const;
bool device_is_shared(const DeviceAddress&) const;
const monitor_device *best_match(const DeviceAddress&) const;
const mount_volume *best_match(const VolumeAddress&) const;
bool operator == (const config&) const;
// Modifiers
void add_ignore_device(const DeviceAddress&);
void add_ignore_volume(const VolumeAddress&);
void add_monitor_device(const DeviceAddress&, int inschk, int rmvchk);
void add_mount_volume(const VolumeAddress&,
const char *dir, const char *opts,
bool is_shared, bool is_unshared);
void add_share_device(const DeviceAddress&);
private:
DeviceAddress *ignore_devices;
VolumeAddress *ignore_volumes;
monitor_device *monitor_devices;
mount_volume *mount_volumes;
share_device *share_devices;
unsigned int n_ignore_devices;
unsigned int n_ignore_volumes;
unsigned int n_monitor_devices;
unsigned int n_mount_volumes;
unsigned int n_share_devices;
unsigned int n_ignore_devices_alloc;
unsigned int n_ignore_volumes_alloc;
unsigned int n_monitor_devices_alloc;
unsigned int n_mount_volumes_alloc;
unsigned int n_share_devices_alloc;
config(const config&); // Do not copy
void operator = (const config&); // or assign.
};
static config *current_config;
static FILE *cf;
Enumerable::Set Config::all_configs;
bool Config::initialized = false;
FileMonitor *Config::config_monitor;
const char *Config::config_path = "/etc/config/mediad.config";
Config::Config(ConfigProc proc, void *closure)
: Enumerable(all_configs),
_proc(proc),
_closure(closure)
{
if (!initialized)
init();
}
Config::~Config()
{ }
bool
Config::device_is_ignored(const DeviceAddress& da)
{
return current_config ? current_config->device_is_ignored(da) : false;
}
bool
Config::volume_is_ignored(const VolumeAddress& va)
{
return current_config ? current_config->volume_is_ignored(va) : false;
}
bool
Config::device_is_mentioned(const DeviceAddress& da)
{
if (current_config && current_config->best_match(da))
return true;
else
return false;
}
// N.B. This returns true iff the volume is mentioned by name in the
// config file.
bool
Config::volume_is_mentioned(const VolumeAddress& va)
{
if (current_config && current_config->best_match(va))
return true;
else
return false;
}
bool
Config::device_is_shared(const DeviceAddress& da)
{
return current_config ? current_config->device_is_shared(da) : false;
}
unsigned int
Config::device_inschk(const DeviceAddress& da)
{
const config::monitor_device *dev = NULL;
if (current_config)
dev = current_config->best_match(da);
if (dev)
return dev->inschk;
else
return DEFAULT_INSCHK_INTERVAL;
}
unsigned int
Config::device_rmvchk(const DeviceAddress& da)
{
const config::monitor_device *dev = NULL;
if (current_config)
dev = current_config->best_match(da);
if (dev)
return dev->rmvchk;
else
return DEFAULT_RMVCHK_INTERVAL;
}
const char *
Config::mount_dir(const VolumeAddress& va)
{
const config::mount_volume *vol = NULL;
if (current_config)
vol = current_config->best_match(va);
if (vol)
return vol->dir;
else
return NULL;
}
const char *
Config::mount_options(const VolumeAddress& va)
{
const config::mount_volume *vol = NULL;
if (current_config)
vol = current_config->best_match(va);
if (vol)
return vol->opts;
else
return NULL;
}
bool
Config::mount_is_shared(const VolumeAddress& va)
{
const config::mount_volume *vol = NULL;
if (current_config)
vol = current_config->best_match(va);
if (vol)
return vol->is_shared;
else
return false;
}
bool
Config::mount_is_unshared(const VolumeAddress& va)
{
const config::mount_volume *vol = NULL;
if (current_config)
vol = current_config->best_match(va);
if (vol)
return vol->is_unshared;
else
return false;
}
///////////////////////////////////////////////////////////////////////////////
// config methods
config::config()
{
ignore_devices = NULL;
ignore_volumes = NULL;
monitor_devices = NULL;
mount_volumes = NULL;
share_devices = NULL;
n_ignore_devices = 0;
n_ignore_volumes = 0;
n_monitor_devices = 0;
n_mount_volumes = 0;
n_share_devices = 0;
n_ignore_devices_alloc = 0;
n_ignore_volumes_alloc = 0;
n_monitor_devices_alloc = 0;
n_mount_volumes_alloc = 0;
n_share_devices_alloc = 0;
}
config::~config()
{
for (unsigned int i = 0; i < n_mount_volumes; i++)
{ delete [] mount_volumes[i].dir;
delete [] mount_volumes[i].opts;
}
delete [] ignore_devices;
delete [] ignore_volumes;
delete [] monitor_devices;
delete [] mount_volumes;
delete [] share_devices;
}
bool
config::device_is_ignored(const DeviceAddress& da) const
{
for (unsigned int i = 0; i < n_ignore_devices; i++)
if (ignore_devices[i] == da)
return true;
return false;
}
bool
config::volume_is_ignored(const VolumeAddress& va) const
{
for (unsigned int i = 0; i < n_ignore_volumes; i++)
if (ignore_volumes[i] == va)
return true;
return false;
}
bool
config::device_is_shared(const DeviceAddress& da) const
{
for (unsigned int i = 0; i < n_share_devices; i++)
if (share_devices[i].da == da)
return true;
return false;
}
const config::monitor_device *
config::best_match(const DeviceAddress& da) const
{
for (unsigned i = 0; i < n_monitor_devices; i++)
if (monitor_devices[i].da == da)
return &monitor_devices[i];
return NULL;
}
const config::mount_volume *
config::best_match(const VolumeAddress& va) const
{
for (unsigned i = 0; i < n_mount_volumes; i++)
if (mount_volumes[i].va == va)
return &mount_volumes[i];
return NULL;
}
bool
config::operator == (const config& that) const
{
unsigned int i;
// Quick checks
if (this == &that)
return true;
if (n_ignore_devices != that.n_ignore_devices)
return false;
if (n_ignore_volumes != that.n_ignore_volumes)
return false;
if (n_monitor_devices != that.n_monitor_devices)
return false;
if (n_mount_volumes != that.n_mount_volumes)
return false;
if (n_share_devices != that.n_share_devices)
return false;
// Compare ignore_devices.
for (i = 0; i < n_ignore_devices; i++)
if (!that.device_is_ignored(ignore_devices[i]))
return false;
// Compare ignore_volumes.
for (i = 0; i < n_ignore_volumes; i++)
if (!that.volume_is_ignored(ignore_volumes[i]))
return false;
// Compare monitor_devices.
for (i = 0; i < n_monitor_devices; i++)
{ const monitor_device *here = &monitor_devices[i];
const monitor_device *there = that.best_match(here->da);
if (!there ||
here->da != there->da ||
here->inschk != there->inschk ||
here->rmvchk != there->rmvchk)
return false;
}
// Compare mount_volumes.
for (i = 0; i < n_mount_volumes; i++)
{ const mount_volume *here = &mount_volumes[i];
const mount_volume *there = that.best_match(here->va);
if (!there)
return false;
if (here->va != there->va)
return false;
if (here->dir && !there->dir || there->dir && !here->dir)
return false;
if (here->dir && there->dir && strcmp(here->dir, there->dir))
return false;
if (here->opts && !there->opts || there->opts && !here->opts)
return false;
if (here->opts && there->opts && strcmp(here->opts, there->opts))
return false;
if (here->is_shared != there->is_shared)
return false;
if (here->is_unshared != there->is_unshared)
return false;
}
// Compare share_devices
for (i = 0; i < n_share_devices; i++)
if (!that.device_is_shared(share_devices[i].da))
return false;
return true;
}
void
config::add_ignore_device(const DeviceAddress& da)
{
for (unsigned int i = 0; i < n_ignore_devices; i++)
if (ignore_devices[i] == da)
{ Log::error("same device ignored twice in /etc/mediad/mediad.config");
return;
}
if (n_ignore_devices >= n_ignore_devices_alloc)
{ n_ignore_devices_alloc = 2 * n_ignore_devices_alloc + 3;
DeviceAddress *p = new DeviceAddress[n_ignore_devices_alloc];
for (unsigned int i = 0; i < n_ignore_devices; i++)
p[i] = ignore_devices[i];
delete [] ignore_devices;
ignore_devices = p;
}
ignore_devices[n_ignore_devices++] = da;
}
void
config::add_ignore_volume(const VolumeAddress& va)
{
for (unsigned int i = 0; i < n_ignore_volumes; i++)
if (ignore_volumes[i] == va)
{ Log::error("same filesystem ignored twice in /etc/mediad/mediad.config");
return;
}
if (n_ignore_volumes >= n_ignore_volumes_alloc)
{ n_ignore_volumes_alloc = 2 * n_ignore_volumes_alloc + 3;
VolumeAddress *p = new VolumeAddress[n_ignore_volumes_alloc];
for (unsigned int i = 0; i < n_ignore_volumes; i++)
p[i] = ignore_volumes[i];
delete [] ignore_volumes;
ignore_volumes = p;
}
ignore_volumes[n_ignore_volumes++] = va;
}
void
config::add_monitor_device(const DeviceAddress& da, int inschk, int rmvchk)
{
for (unsigned int i = 0; i < n_monitor_devices; i++)
if (monitor_devices[i].da == da)
{ Log::error("same device monitored twice in /etc/mediad/mediad.config");
return;
}
if (n_monitor_devices >= n_monitor_devices_alloc)
{ n_monitor_devices_alloc = 2 * n_monitor_devices_alloc + 3;
monitor_device *p = new monitor_device[n_monitor_devices_alloc];
for (unsigned int i = 0; i < n_monitor_devices; i++)
p[i] = monitor_devices[i];
delete [] monitor_devices;
monitor_devices = p;
}
monitor_devices[n_monitor_devices].da = da;
monitor_devices[n_monitor_devices].inschk = inschk;
monitor_devices[n_monitor_devices].rmvchk = rmvchk;
n_monitor_devices++;
}
void
config::add_mount_volume(const VolumeAddress& va,
const char *dir, const char *opts,
bool is_shared, bool is_unshared)
{
for (unsigned int i = 0; i < n_mount_volumes; i++)
if (mount_volumes[i].va == va)
{ Log::error("same filesystem mounted twice in /etc/mediad/mediad.config");
return;
}
if (n_mount_volumes >= n_mount_volumes_alloc)
{ n_mount_volumes_alloc = 2 * n_mount_volumes_alloc + 3;
mount_volume *p = new mount_volume[n_mount_volumes_alloc];
for (unsigned int i = 0; i < n_mount_volumes; i++)
p[i] = mount_volumes[i];
delete [] mount_volumes;
mount_volumes = p;
}
mount_volumes[n_mount_volumes].va = va;
mount_volumes[n_mount_volumes].dir =
(dir && *dir) ? strdupplus(dir) : NULL;
mount_volumes[n_mount_volumes].opts =
(opts && *opts) ? strdupplus(opts) : NULL;
mount_volumes[n_mount_volumes].is_shared = is_shared;
mount_volumes[n_mount_volumes].is_unshared = is_unshared;
n_mount_volumes++;
}
void
config::add_share_device(const DeviceAddress& da)
{
for (unsigned int i = 0; i < n_share_devices; i++)
if (share_devices[i].da == da)
{ Log::error("same device shared twice in /etc/mediad/mediad.config");
return;
}
if (n_share_devices >= n_share_devices_alloc)
{ n_share_devices_alloc = 2 * n_share_devices_alloc + 3;
share_device *p = new share_device[n_share_devices_alloc];
for (unsigned int i = 0; i < n_share_devices; i++)
p[i] = share_devices[i];
delete [] share_devices;
share_devices = p;
}
share_devices[n_share_devices++].da = da;
}
///////////////////////////////////////////////////////////////////////////////
// initialization and config file parsing.
enum Token {
DEVICE,
DIRECTORY,
FILESYSTEM,
IGNORE,
INSCHK,
MONITOR,
MOUNT,
OPTIONS,
PARTITION,
RMVCHK,
SHARE,
SHARED,
UNSHARED,
PATH,
NUMBER,
STRING,
ENDLINE,
ENDFILE
};
union YYSTYPE {
int number;
char string[MNTMAXSTR];
} yylval;
static bool token_is_saved = false;
static Token last_token;
static unsigned int line_no;
static char linebuffer[1024];
static int ungot_char = EOF;
static char *charp;
int
Config::yygetchar()
{
assert(charp == NULL || charp >= linebuffer && charp < linebuffer + sizeof linebuffer);
if (ungot_char != EOF)
{ int c = ungot_char;
ungot_char = EOF;
return c;
}
if (!charp || !*charp)
{
assert(cf != NULL);
charp = fgets(linebuffer, sizeof linebuffer, cf);
line_no++;
}
if (!charp)
return EOF;
return *charp++;
}
void
Config::yyungetchar(int c)
{
ungot_char = c;
}
void
Config::error(const char *msg)
{
Log::error("error parsing %s: %s", config_path, msg);
if (charp)
Log::error("context: line %d after \"%.*s\"",
line_no, charp - linebuffer, linebuffer);
else
Log::error("context: end of file %s", config_path);
}
void
Config::yyunlex()
{
assert(!token_is_saved);
token_is_saved = true;
}
Token
Config::yylex()
{
if (token_is_saved)
{ token_is_saved = false;
return last_token;
}
struct kw {
char *name;
Token value;
};
int i, c;
struct kw *p;
static struct kw keywords[] = {
{ "device", DEVICE },
{ "directory", DIRECTORY },
{ "filesystem", FILESYSTEM },
{ "ignore", IGNORE },
{ "inschk", INSCHK },
{ "monitor", MONITOR },
{ "mount", MOUNT },
{ "options", OPTIONS },
{ "partition", PARTITION },
{ "rmvchk", RMVCHK },
{ "share", SHARE },
{ "shared", SHARED },
{ "unshared", UNSHARED },
{ NULL }
};
do
c = yygetchar();
while (c == ' ' || c == '\t'); // Skip whitespace. (not newlines)
if (c == '#') // Skip comments.
while ((c = yygetchar()) != EOF && c != '\n')
continue;
// Fall through after skipping comment...
if (c == EOF)
return last_token = ENDFILE;
else if (c == '\n')
return last_token = ENDLINE;
if (isascii(c) && isdigit(c)) // Scan a number.
{ int n = 0;
do
{ n = 10 * n + c - '0';
c = yygetchar();
} while (isascii(c) && isdigit(c));
yyungetchar(c);
yylval.number = n;
return last_token = NUMBER;
}
// Whatever is left must be a path, a string or a keyword.
i = 0;
do
{ if (i < sizeof yylval.string - 1)
yylval.string[i++] = c;
c = yygetchar();
} while (isascii(c) && !isspace(c));
yyungetchar(c);
yylval.string[i] = '\0';
// Is it a keyword?
for (p = keywords; p->name != NULL; p++)
if (!strcmp(yylval.string, p->name))
return last_token = p->value;
// Not a keyword. Either a path or a string.
return last_token = (yylval.string[0] == '/' ? PATH : STRING);
}
bool
Config::parse_device(DeviceAddress *da)
{
Token token = yylex();
if (token == PATH)
{
// Convert path to a VolumeAddress. Verify that the VolumeAddress
// refers to a single partition, then extract the device part
// of that partition's address.
VolumeAddress va(yylval.string);
if (va.valid() && !va.partition(1))
*da = va.partition(0)->device();
return true;
}
else
{ error("expected a path");
return false;
}
}
FormatIndex
Config::match_fstype(const char *type)
{
struct {
char *name;
FormatIndex index;
} table[] = {
{ "audio", FMT_CDDA },
{ "efs", FMT_EFS },
{ "hfs", FMT_HFS },
{ "mac", FMT_HFS },
{ "dos", FMT_DOS },
{ "fat", FMT_DOS },
{ "xfs", FMT_XFS },
{ "iso9660", FMT_ISO },
{ "cdda", FMT_CDDA },
{ "music", FMT_CDDA },
{ NULL, FMT_UNKNOWN }
}, *p;
for (p = table; p->name && strcmp(p->name, type); p++)
continue;
return p->index;
}
// Volume description has two forms.
// path type
// path type partition N
bool
Config::parse_volume(VolumeAddress *va)
{
char path[MNTMAXSTR];
Token token = yylex();
if (token != PATH)
{ error("expected a path");
return false;
}
strcpy(path, yylval.string);
token = yylex();
if (token != STRING)
{ error("expected a filesystem type");
return false;
}
FormatIndex type = match_fstype(yylval.string);
if (type == FMT_UNKNOWN)
{ error("unrecognized filesystem type");
return false;
}
int partno = PartitionAddress::UnknownPartition;
token = yylex();
if (token == PARTITION)
{ token = yylex();
if (token != NUMBER)
{ error("expected a number");
return false;
}
partno = yylval.number;
}
else
yyunlex();
*va = VolumeAddress(path, type, partno);
return true;
}
bool
Config::parse_line(config *config)
{
DeviceAddress da;
VolumeAddress va;
Token token = yylex();
if (token == ENDFILE)
return false;
if (token == IGNORE)
{ token = yylex();
if (token == DEVICE)
{
if (parse_device(&da))
config->add_ignore_device(da);
}
else if (token == FILESYSTEM)
{
if (parse_volume(&va))
config->add_ignore_volume(va);
}
else
{ error("expected `device' or `filesystem'");
return false;
}
token = yylex();
}
else if (token == MONITOR)
{ token = yylex();
if (token != DEVICE)
{ error("expected `device'");
return false;
}
if (!parse_device(&da))
return false;
int inschk = DEFAULT_INSCHK_INTERVAL;
int rmvchk = DEFAULT_RMVCHK_INTERVAL;
while ((token = yylex()) != ENDLINE)
if (token == INSCHK)
{ token = yylex();
if (token != NUMBER)
{ error("expected a number");
return false;
}
inschk = yylval.number;
}
else if (token == RMVCHK)
{ token = yylex();
if (token != NUMBER)
{ error("expected a number");
return false;
}
rmvchk = yylval.number;
}
else
{ error("expected `inschk' or `rmvchk'");
return false;
}
config->add_monitor_device(da, inschk, rmvchk);
}
else if (token == MOUNT)
{ token = yylex();
if (token != FILESYSTEM)
{ error("expected `filesytem'");
return false;
}
if (!parse_volume(&va))
return false;
char dir[MNTMAXSTR] = "";
char opts[MNTMAXSTR] = "";
bool is_shared = false, is_unshared = false;
while ((token = yylex()) != ENDLINE)
if (token == DIRECTORY)
{ token = yylex();
if (token != PATH)
{ error("expected a directory");
return false;
}
strcpy(dir, yylval.string);
}
else if (token == OPTIONS)
{ token = yylex();
if (token != STRING)
{ error("expected an options string");
return false;
}
strcpy(opts, yylval.string);
}
else if (token == SHARED)
is_shared = true;
else if (token == UNSHARED)
is_unshared = true;
else
{ error("expected `directory' or `options'");
return false;
}
if (is_shared && is_unshared)
{ Log::error("filesystem can't be both shared and unshared");
return false;
}
config->add_mount_volume(va, dir, opts, is_shared, is_unshared);
}
else if (token == SHARE)
{ token = yylex();
if (token == DEVICE)
{ if (!parse_device(&da))
return false;
config->add_share_device(da);
}
else
{ error("expected `device'");
return false;
}
token = yylex(); // Get ENDLINE.
}
else if (token != ENDLINE)
{
error("expected `ignore', `monitor', `mount' or `share'");
return false;
}
return token == ENDLINE;
}
config *
Config::parse_config()
{
cf = fopen(config_path, "r");
if (!cf)
return NULL;
config *p = new config;
line_no = 0;
token_is_saved = false;
charp = 0;
ungot_char = EOF;
while (parse_line(p))
continue;
fclose(cf);
cf = NULL;
if (last_token != ENDFILE) // syntax error
{ delete p;
p = NULL;
}
return p;
}
void
Config::init()
{
assert(!initialized);
initialized = true;
current_config = parse_config();
config_monitor = new FileMonitor(config_path, change_proc, NULL);
}
void
Config::change_proc(FAMonitor&, const FAMEvent& event, void *)
{
bool broadcast_needed = false;
if (event.code != FAMExists && event.code != FAMEndExist &&
event.code != FAMAcknowledge &&
(event.code != FAMDeleted || current_config != NULL))
{
Log::debug("%s changed. Rereading.", config_path);
config *new_config = parse_config();
if (new_config && current_config && *new_config != *current_config ||
new_config || current_config)
{ delete current_config;
current_config = new_config;
broadcast_needed = true;
}
}
// Tell everybody that something changed.
if (broadcast_needed)
for (Config *p = first(); p; p = p->next())
(*p->_proc)(*p, p->_closure);
}