#include "netpi.h" /* * NOTE: I am well aware C++ provides many constructs that would * drastically reduce the amount of code in this file. However * at the present moment, I am trying to keep this C-pure for * various reasons. */ /* * The idea is the config file needs not only to be parsable by * this daemon, but it's line ordering and all content should * also be persisted on a rewrite of the config file after it * changes; including comments and white space in most cases. * * To that end, this is read in like a text editor would. * Each line is kept in a node list below. When values need * to be updated, the list is searched in a find and replace * mode. That way the re-written file only has minimal changes * and the human author's whitespace, comments, and formatting * is preserved. */ typedef struct _st_config_line { struct _st_config_line *next; char *token, *value; // Only linked if line is token=value char *section; // Only linked if line is section header char *text; // Trimmed line text int lineno; // line number starting at 1 } config_line_t; static config_line_t *topline = NULL, *botline = NULL; static inline int _whitespace (char ch) { return ((ch == ' ') || (ch == '\t') || (ch == '\n') || (ch == '\r')) ? 1 : 0; } static inline char *_trim (char *text) { while (*text && _whitespace(*text)) text++; int len = strlen (text); while (len && _whitespace (text[len - 1])) len--; text[len] = '\0'; return text; } typedef struct _st_config_drive { struct _st_config_drive *next; char *id; char *label; char *path; char *file; int cyls; int heads; int spt; uint32_t sectors; int readonly; int lineno; } config_drive_t; typedef struct _st_config { char *img_path; char *i2c_path; char *timezone; config_drive_t *topdrive, *botdrive; char *master_id; char *slave_id; } config_t; static config_t *config = NULL; int config_load (const char *path) { config_free (); // Create system defaults config = (config_t *) calloc (sizeof (config_t), 1); config->img_path = strdup ("/mnt"); config->i2c_path = strdup (I2C_EFB_DEVICE); config->timezone = strdup ("America/New_York"); int sts = -1; FILE *handle = NULL; do { if (!(handle = fopen (path, "r"))) { LOGERR("Unable to open config file '%s' - %s", path, strerror (errno)); break; } int lineno = 0; char buffer[1024], *ptr, *section = NULL; config_drive_t *drive = NULL; while ((ptr = fgets (buffer, sizeof (buffer), handle))) { config_line_t *line = (config_line_t *) calloc (sizeof (config_line_t), 1); ptr = line->text = strdup (_trim (ptr)); if (botline) botline->next = line; else topline = line; botline = line; line->lineno = lineno = lineno + 1; if (!ptr[0] || (ptr[0] == '#')) continue; int len = strlen (ptr); if (ptr[0] == '[') { drive = NULL; if (ptr[len - 1] != ']') { LOGERR("Invalid section syntax at line %d", lineno); break; } ptr[len - 1] = '\0'; ptr = _trim(ptr + 1); // Validate section names if (strncasecmp (ptr, "drive", 5) == 0) { char *space = strchr (ptr, ' '); if (!space) { LOGERR("Drive sections must have id at line %d", lineno); break; } *space = '\0'; ptr = _trim (ptr); line->value = _trim (space + 1); // Perform a duplicate check drive = config->topdrive; while (drive) { if (strcasecmp (drive->id, line->value) == 0) { LOGERR("Duplicate drive ID '%s' lines %d/%d", line->value, lineno, drive->lineno); break; } drive = drive->next; } if (drive) break; drive = (config_drive_t *) calloc (sizeof (config_drive_t), 1); drive->id = strdup (line->value); drive->lineno = lineno; drive->spt = 63; drive->heads = 16; if (config->botdrive) config->botdrive->next = drive; else config->topdrive = drive; config->botdrive = drive; } else if ((strcasecmp (ptr, "system") != 0) && (strcasecmp (ptr, "rtc") != 0) && (strcasecmp (ptr, "network") != 0)) { LOGERR("Unknown section name '%s' at %s:%d", path, ptr, lineno); break; } section = line->section = ptr; continue; } char *equal = strchr (ptr, '='); if (equal) { if (!section) { LOGERR("Value assignment outside of section at line %d", lineno); break; } *equal = '\0'; line->value = _trim (equal + 1); line->token = _trim (ptr); if (!line->token[0] || !line->value[0]) { LOGERR("Malformed token=value pair on line %d", lineno); break; } printf ("joe ['%s'] '%s' = '%s'\n", section, line->token, line->value); struct stat stinfo; if (strcasecmp (section, "system") == 0) { if (strcasecmp (line->token, "img_path") == 0) { int len = strlen (line->value); if (line->value[len - 1] != '/') strcat (line->value, "/"); if (stat (line->value, &stinfo)) { LOGERR("img_path '%s' (line %d) not found", line->value, lineno); break; } free (config->img_path); config->img_path = strdup (line->value); } else if (strcasecmp (line->token, "i2c_path") == 0) { if (stat (line->value, &stinfo)) { LOGERR("i2c_path '%s' (line %d) not found", line->value, lineno); break; } free (config->i2c_path); config->i2c_path = strdup (line->value); } else if (strcasecmp (line->token, "master") == 0) { if (config->master_id) free (config->master_id); config->master_id = strdup (line->value); } else if (strcasecmp (line->token, "slave") == 0) { if (config->slave_id) free (config->slave_id); config->slave_id = strdup (line->value); } else { LOGERR("Unknown system token '%s' on line %d", line->token, lineno); break; } } else if (strcasecmp (section, "drive") == 0) { if (strcasecmp (line->token, "label") == 0) { if (drive->label) free (drive->label); drive->label = strdup (line->value); } else if (strcasecmp (line->token, "cyls") == 0) { if (!(drive->cyls = atoi (line->value))) { LOGERR("Invalid cylinder count (%d) on line %d", drive->cyls, lineno); break; } } else if (strcasecmp (line->token, "heads") == 0) { drive->heads = atoi (line->value); if ((drive->heads < 2) || (drive->heads > 255)) { LOGERR("Invalid head count (%d) on line %d", drive->heads, lineno); break; } } else if (strcasecmp (line->token, "spt") == 0) { drive->spt = atoi (line->value); if ((drive->spt < 2) || (drive->spt > 255)) { LOGERR("Invalid SPT count (%d) on line %d", drive->spt, lineno); break; } } else if (strcasecmp (line->token, "sectors") == 0) { if (!(drive->sectors = strtoull (line->value, NULL, 10))) { LOGERR("Invalid sector count (%u) on line %d", drive->sectors, lineno); break; } } else if (strcasecmp (line->token, "readonly") == 0) { if (strcasecmp (line->value, "true") == 0) drive->readonly = 1; else drive->readonly = atoi (line->value); } else if (strcasecmp (line->token, "path") == 0) { if (drive->path) free (drive->path); drive->path = strdup (line->value); } else if (strcasecmp (line->token, "file") == 0) { if (drive->file) free (drive->file); drive->file = strdup (line->value); } else { LOGERR("Unknown drive token '%s' on line %d", line->token, lineno); break; } } continue; } // Bad text LOGERR("Parse error on line %d", lineno); break; } if (ptr) break; // Propagate config forward // Drive validation drive = config->topdrive; while (drive) { if (!drive->path) { if (!drive->file) { LOGERR("No path given for drive '%s' @ line %d", drive->id, drive->lineno); break; } int len = strlen (drive->file) + strlen (config->img_path) + 2; drive->path = (char *) malloc (len); strcpy (drive->path, config->img_path); strcat (drive->path, drive->file); } else if (drive->file) { LOGERR("Both path and file given for drive '%s' @ line %d", drive->id, drive->lineno); break; } struct stat64 stinfo; if (stat64 (drive->path, &stinfo)) { LOGERR("Unable to locate drive '%s' path '%s' - %s", drive->id, drive->path, strerror (errno)); break; } if (stinfo.st_size & 0x1ff) { LOGERR("Drive '%s' size not divisible by 512", drive->id); break; } if (!drive->sectors) drive->sectors = stinfo.st_size >> 9; if (drive->cyls == 0) drive->cyls = drive->sectors / (drive->spt * drive->heads); uint32_t tsec = (uint32_t) drive->cyls * (uint32_t) drive->spt * (uint32_t) drive->heads; if (tsec > drive->sectors) { LOGERR("Drive '%s' CHS %u/%d/%d (%u) > apparent sectors " "(%u)", drive->id, drive->cyls, drive->heads, drive->spt, tsec, drive->sectors); break; } LOGDBG("Loaded drive [%s] from '%s' CHS=%u/%d/%d (%u)\n", drive->id, drive->path, drive->cyls, drive->heads, drive->spt, drive->sectors); drive = drive->next; } if (drive) break; // verify master id and slave id resolve if (config->master_id) { drive = config->topdrive; while (drive) { if (strcasecmp (config->master_id, drive->id) == 0) break; drive = drive->next; } if (!drive) { LOGERR("Unable to find master drive '%s'", config->master_id); break; } } if (config->slave_id) { drive = config->topdrive; while (drive) { if (strcasecmp (config->slave_id, drive->id) == 0) break; drive = drive->next; } if (!drive) { LOGERR("Unable to find slave drive '%s'", config->slave_id); break; } } sts = 0; } while (0); if (handle) fclose (handle); if (sts) config_free (); return sts; } void config_free (void) { if (config) { if (config->img_path) free (config->img_path); if (config->i2c_path) free (config->i2c_path); if (config->timezone) free (config->timezone); config_drive_t *next, *drive = config->topdrive; while (drive) { if (drive->id) free (drive->id); if (drive->label) free (drive->label); if (drive->path) free (drive->path); next = drive->next; free (drive); drive = next; } free (config); config = NULL; } // Free config file editor cache config_line_t *next, *line = topline; while (line) { if (line->token) free (line->token); if (line->value) free (line->value); if (line->section) free (line->section); if (line->text) free (line->text); next = line->next; free (line); line = next; } topline = botline = NULL; return; } drive_t *config_drive_init (int master) { if (!config) return NULL; char *id = master ? config->master_id : config->slave_id; if (!id) return NULL; config_drive_t *cdrive = config->topdrive; while (cdrive) { if (strcasecmp (id, cdrive->id) == 0) break; cdrive = cdrive->next; } if (!cdrive) return NULL; int fd = open (cdrive->path, (cdrive->readonly ? O_RDONLY : O_RDWR) | O_LARGEFILE); if (fd < 0) { LOGERR("Unable to open image file '%s' - %s", cdrive->path, strerror (errno)); return NULL; } drive_t *drive = (drive_t *) calloc (sizeof (drive_t), 1); drive->fd = fd; drive->cyls = cdrive->cyls; drive->heads = cdrive->heads; drive->spt = cdrive->spt; drive->sectors = cdrive->sectors; drive->readonly = cdrive->readonly; drive->path = strdup (cdrive->path); if (cdrive->label) drive->label = strdup (cdrive->label); else drive->label = master ? "NetPi-IDE_Master" : "NetPi-IDE_Slave"; return drive; } void config_drive_term (drive_t *drive) { if (drive) { if (drive->fd >= 0) close (drive->fd); if (drive->path) free (drive->path); if (drive->label) free (drive->label); free (drive); } } const char *config_i2c_path (void) { return config ? config->i2c_path : I2C_EFB_DEVICE; } #if 0 int main (int argc, char *argv[]) { if (argc > 1) { printf ("config %d\n", config_load ("netpi.cfg")); #if 0 config_line_t *node = topline; while (node) { if (node->section) { if (node->value) printf ("%4d: [%s %s]\n", node->lineno, node->section, node->value); else printf ("%4d: [%s]\n", node->lineno, node->section); } else if (node->token) { printf ("%4d: %s = %s\n", node->lineno, node->token, node->value); } else { printf ("%4d: %s\n", node->lineno, node->text); } node = node->next; } #endif } return 0; } #endif