#include "Config.H" #include #include #include #include #include // for DEFAULT_*CHK_INTERVAL #include #include #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); }