Script Parser - initrd besser in rdfs integrieren
-
Hallo miteinander,
ich habe momentan eine initrd in Anlehnung an James Molloys Umsetzung programmiert und kann bisher gut damit arbeiten, jedoch habe ich keine Ordner in der initrd. Nun habe ich mir gedacht, dass ich ein kleines Script hinterlegen kann, das in einer Pseudo-Shell-Script-Form geschrieben ist und geparst werden kann. Dabei sollen die Files unter /* in die jeweiligen Ordner der RamDisk geschoben werden, anstatt das schrecklich mit einem begrenztem Array zu indizieren und nicht weiter anwachsen zu lassen. Die meisten I/O-Befehle für Files habe ich schon auf Low-Level-Ebene implementiert (open, close, write, read, creat, ...). Bisher habe ich alle meine Compiler/Parser in Managed Code geschrieben und über Reguläre Ausdrücke alles ausgewertet - nun muss ich zeichenweise in C damit kämpfen
Mein Ansatz war der, dass ich Zeile für Zeile parse (entspricht Command für Command - "cp /GalileoShell.conf /Libraries/Preferences;" ist eine solche Command) und die Zeilen zeichenweise weiterverarbeite. Momentan habe ich einen größeren DFA (deterministic finite automaton), der vom Zustand READY je nach nächstem Zeichen in weitere Zustände übergeht, wie z.B. C (c wurde eingelesen), CP (es ist der cp-Befehl) oder READINPUT (Argument bis SPACE lesen). So kann ich auch recht einfach Fehlerbehandlung betreiben! Meine Frage wäre nun: Ist dies effektiv genug? Was ist zu empfehlen?
Falls noch Erklärungsbedarf besteht, kann ich auch Teile des Codes posten, wenn nötig.
Gruß,
Christian Ivicevic
-
Ich denke nicht, dass directories notwendig sind in der initrd. Erkläre mal wozu das bei dir eingesetzt wird. An Sourcecode wäre ich dennoch interessiert, da wir auch über ein verbessertes RamDisk File Format nachdenken.
-
Es ist so, dass ich unglaublich ordentlich bin bzw. es versuche und es daher nicht so "schön" finde, wenn alle meine Dateien irgendwie verstreut sind. Daher habe ich mir gedacht, dass ich die Dateien aus der InitRD in mein RDFS kopiere um so eine Ordnung zu bekommen.
Ich bin mir unsicher, welchen Code du gerne sehen würdest, weshalb ich einfach mal meinen Parser hier auszugsweise poste. (In der aktuellen Version ist das lediglich ein schnell gecodeter Wisch, der weder so effizient wie möglich, noch verständlich geschrieben wurde ;))
// State enumeration of the Galileo Shell Automaton. typedef enum GSH_STATE { GSH_ready = 0, // Automaton is ready for input. GSH_in, // ReadInput state: read input until we reach a space. GSH_c, // We have read a 'c' char. GSH_cp, // We have the 'cp' command. GSH_cp_readsrc, // arg1: Whats the src file? GSH_cp_readdest, // arg2: Where to copy? GSH_m, // We have read a 'm' char. GSH_mk, // We (may) have the 'mkdir' command. GSH_mkdir, // Yep, we have it! GSH_mv // We have the 'mv' command. } gsh_state_t; // Parser // Param is pointer to the code to parse. No syntax checking here! void gsh(void *code) { char *base = (char *)code, *c = (char *)code; off_t linebuf = NULL; while(1) { while(*c != ';' && *c != '.') // ; seperates commands, . is the end of the script. *c++; if(*c == '.') return; linebuf = malloc((size_t)c - (size_t)base, 0, NULL); memcpy((void *)linebuf, base, (size_t)c - (size_t)base); // PARSER char *ptr = (char *)linebuf; gsh_state_t state = GSH_ready; gsh_state_t nstate = GSH_ready; int exit = 0; int ignore = 0; buf_t arg1[128], arg2[128]; // Temp buf_t *current_buffer = NULL; while(!exit && *ptr != '\0') { switch(state) { case GSH_ready: if(*ptr == 'c') state = GSH_c; if(state == GSH_c && *(ptr+1) == 'p') // cp is the only command with c at first pos. Check it now! state = GSH_cp; if(*ptr == 'm') state = GSH_m; if(state == GSH_ready) // Unknown command. goto def; break; case GSH_cp: nstate = GSH_cp_readsrc; state = GSH_in; current_buffer = arg1; *ptr++; break; case GSH_cp_readsrc: current_buffer = arg2; nstate = GSH_cp_readdest; state = GSH_in; break; case GSH_cp_readdest: exit = 1; ssize_t sz = 0; strcat((char *)arg2, (char *)arg1); int fd = open((const char *)arg2, O_RDWR | O_CREAT); if(fd < 0) printk("[gsh ] error opening dest file.\n"); else { int src = open((char *)arg1, O_RDONLY); if(src < 0) printk("[gsh ] error opening src file.\n"); else { buf_t *buf = (buf_t *)malloc(1024, 0, 0); sz = read(src, buf, 1024); write(fd, buf, sz); free(buf); close(src); close(fd); printk("[gsh ] Copied the file `%s' (%d bytes) to `%s'\n", arg1, sz-1, arg2); } } break; case GSH_in: if(*ptr != ' ' && *ptr != ';') *current_buffer++ = *ptr; else { ignore = 1; *current_buffer = '\0'; state = nstate; } break; case GSH_m: if(*ptr == 'k') state = GSH_mk; if(state == GSH_mk && *(ptr+1) == 'd' && *(ptr+2) == 'i' && *(ptr+3) == 'r') state = GSH_mkdir; if(*ptr == 'v') state = GSH_mv; if(state == GSH_m) goto def; break; case GSH_mkdir: exit = 1; // TODO: Implement this. break; case GSH_mv: exit = 1; // TODO: Implement this. break; def: default: printk("[gsh ] parse exception! aborting gsh.\n"); exit = 1; } if(!ignore && !exit) *ptr++; else ignore = 0; } // PARSER END free((void *)linebuf); *c++; base = c; } PANIC("[gsh ] WTF?! how could gsh crash!?"); }
Das dazugehörige GSH-Script welches geladen und an gsh(void übergeben wird ist einfach:
cp /GalileoShell.conf /Libraries/Preferences;mv /GalileoShell.conf /System/TestFolder;mkdir /Users/foobar;.
Das ist so in der letzten Nacht entstanden. Leider, ich muss es zugeben, nicht ganz optimal geschrieben, aber es läuft wie es soll!
Gruß,
ChristianPS: Ich habe gerade gesehen, dass mein letztes free für die letzte Zeile nicht aufgerufen wird, da bei der .-Überprüfung oben mit return sofort beendet wird. Aber das wird eh alles neu geschrieben. Primär geht es mir darum zu sehen, ob der grundlegende Gedanke/Ansatz hier korrekt ist.
-
Also wenn ich ehrlich sein soll: Das ist eine äußerst ungewöhnliche Weise, das Problem anzupacken. Wahrscheinlich skaliert das nicht wesentlich über zweieinhalb Befehle hinaus.
-
Aber immerhin kann man schon ein bisschen versuchen einen Interpreter für nachher zu schreiben, wenn man Shell Scripts ausführen will
-
Ja, nur sieht das nachher völlig anders aus. In der Regel führt eine Shell ja vor allem externe Programme aus und nicht eingebaute Befehle. Spätestens dann hast du mit deinem Automaten verloren. Du kannst ja nicht die Namen aller denkbaren Programme in die Shell hartkodieren.
Wofür du einen Automaten sinnvoll verbasteln kannst, ist das Auseinandernehmen der Kommandozeile in Befehl und die verschiedenen Parameter, die durch Leerzeichen getrennt sind. Ausnahmen natürlich für Anführungszeichen oder Escapen mit Backslash usw. Für sowas wäre ein Automat perfekt.