biiiig update folks
This commit is contained in:
parent
52d87dea1b
commit
6ec1cc78f7
4 changed files with 682 additions and 135 deletions
2
.clangd
2
.clangd
|
|
@ -1,2 +1,2 @@
|
||||||
CompileFlags:
|
CompileFlags:
|
||||||
Add: ["-xc"]
|
Add: ["-xc", "-std=gnu23"]
|
||||||
|
|
|
||||||
2
Makefile
2
Makefile
|
|
@ -1,5 +1,5 @@
|
||||||
CC=$(shell which clang)
|
CC=$(shell which clang)
|
||||||
CFLAGS:=$(CFLAGS)
|
CFLAGS:=$(CFLAGS) --std=gnu23
|
||||||
BINDIR=bin
|
BINDIR=bin
|
||||||
|
|
||||||
main=$(BINDIR)/main
|
main=$(BINDIR)/main
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 7343039dc975ebbd3c6660527da0f3780ff432bc
|
Subproject commit 83210aa292843c67e768f03ba3390efe388505bd
|
||||||
811
src/main.c
811
src/main.c
|
|
@ -1,76 +1,221 @@
|
||||||
#include <bits/types/idtype_t.h>
|
// vim:fileencoding=utf-8:foldmethod=marker
|
||||||
#include <bits/types/siginfo_t.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <wait.h>
|
#include <wait.h>
|
||||||
|
|
||||||
#include "linked-list/linked_list.c"
|
#include "linked-list/linked_list.c"
|
||||||
|
|
||||||
|
// Options {{{
|
||||||
|
// 90% non implementate, qui per usi futuri
|
||||||
|
struct options {
|
||||||
|
_Bool a;
|
||||||
|
_Bool C;
|
||||||
|
_Bool e;
|
||||||
|
_Bool f;
|
||||||
|
_Bool h;
|
||||||
|
_Bool n;
|
||||||
|
_Bool u;
|
||||||
|
_Bool v;
|
||||||
|
_Bool x;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct options optStatus = {0};
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// Logging {{{
|
||||||
|
|
||||||
|
// semplice funzione di logging, niente di speciale
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
#define LOG(...) printf(__VA_ARGS__)
|
#define LOG(...) \
|
||||||
|
do { \
|
||||||
|
printf(__VA_ARGS__); \
|
||||||
|
fflush(stdout); \
|
||||||
|
} while (0)
|
||||||
|
// funzione che esegue lo snippet di codice solo qualora il logging sia
|
||||||
|
// abilitato
|
||||||
#define LOG_WRAP(x, ...) x(__VA_ARGS__)
|
#define LOG_WRAP(x, ...) x(__VA_ARGS__)
|
||||||
|
// opposto di LOG_WRAP
|
||||||
|
#define LOG_WRAP_REVERSE(x, ...)
|
||||||
#else
|
#else
|
||||||
#define LOG(...)
|
#define LOG(...)
|
||||||
#define LOG_WRAP(x, ...)
|
#define LOG_WRAP(x, ...)
|
||||||
|
#define LOG_WRAP_REVERSE(x, ...) x(__VA_ARGS__)
|
||||||
|
#endif
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// List extensions {{{
|
||||||
|
|
||||||
|
// estensione alla lista che va a creare una funzione toarray e lo struct
|
||||||
|
// associato
|
||||||
|
#define CREATE_TOARRAY(name, type, format) \
|
||||||
|
struct name##_array_args { \
|
||||||
|
_Bool backwards; \
|
||||||
|
int len; \
|
||||||
|
type* array; \
|
||||||
|
}; \
|
||||||
|
int _##name##_to_array /*a commment is needed to make the syntax highliter \
|
||||||
|
work after this*/ \
|
||||||
|
(name##_node * node, void* vargs) { \
|
||||||
|
struct name##_array_args* args = (struct name##_array_args*)vargs; \
|
||||||
|
LOG("len: %i, backwards: %b, val: " #format \
|
||||||
|
", node: %p, node_next: %p\n", \
|
||||||
|
args->len, args->backwards, node->val, node, node->next); \
|
||||||
|
\
|
||||||
|
if (!args->backwards) { \
|
||||||
|
args->len++; \
|
||||||
|
if (!node->next) { \
|
||||||
|
args->backwards = 1; \
|
||||||
|
args->array = malloc(args->len * sizeof(type)); \
|
||||||
|
if (!args->array) return -1; \
|
||||||
|
} \
|
||||||
|
} else { \
|
||||||
|
args->len--; \
|
||||||
|
args->array[args->len - 1] = node->val; \
|
||||||
|
free(node); \
|
||||||
|
} \
|
||||||
|
return 0; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
type* name##_to_array(name* str) { \
|
||||||
|
struct name##_array_args args = {.backwards = 0, .len = 1}; \
|
||||||
|
if (foreach_twopass_##name(str, _##name##_to_array, &args)) \
|
||||||
|
return NULL; \
|
||||||
|
str->head = NULL; \
|
||||||
|
return args.array; \
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// Strings {{{
|
||||||
|
// clang-format off
|
||||||
|
// clang-format va a dare problemi con il %
|
||||||
|
CREATE_LIST(string_array, char*);
|
||||||
|
CREATE_TOARRAY(string_array, char*, %s);
|
||||||
|
CREATE_LIST(string, char);
|
||||||
|
CREATE_TOARRAY(string, char, %c);
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
//}}}
|
||||||
|
|
||||||
|
// Alias type {{{
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
// uno struct per gli alias, contiene il nome e
|
||||||
|
// o una funzione che va a eseguire (per esempio exit, che va a eseguire __exit())
|
||||||
|
// o un comando e argomenti da aggiungere (per esempio `alias ll="ls -l"
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
struct alias {
|
||||||
|
char* name;
|
||||||
|
char* command;
|
||||||
|
char** args;
|
||||||
|
int (*fn)(char**);
|
||||||
|
};
|
||||||
|
|
||||||
|
// tipo non stampabile facilmente, rimossa temporanemante la funzione di log
|
||||||
|
#undef LOG
|
||||||
|
#define LOG(...)
|
||||||
|
CREATE_LIST(alias_list, struct alias)
|
||||||
|
CREATE_TOARRAY(alias_list, struct alias, '\0')
|
||||||
|
#if DEBUG
|
||||||
|
#undef LOG
|
||||||
|
#define LOG(...) \
|
||||||
|
do { \
|
||||||
|
printf(__VA_ARGS__); \
|
||||||
|
fflush(stdout); \
|
||||||
|
} while (0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define CREATE_TOARRAY(name, type, format) \
|
alias_list aliases = {.head = NULL};
|
||||||
struct name##_array_args { \
|
|
||||||
_Bool backwards; \
|
|
||||||
int len; \
|
|
||||||
type* array; \
|
|
||||||
}; \
|
|
||||||
int _##name##_to_array(name##_node* node, void* vargs) { \
|
|
||||||
struct name##_array_args* args = (struct name##_array_args*)vargs; \
|
|
||||||
LOG("len: %i, backwards: %b, val: " #format \
|
|
||||||
", node: %p, node_next: %p\n", \
|
|
||||||
args->len, args->backwards, node->val, node, node->next); \
|
|
||||||
\
|
|
||||||
if (!args->backwards) { \
|
|
||||||
args->len++; \
|
|
||||||
if (!node->next) { \
|
|
||||||
args->backwards = 1; \
|
|
||||||
args->array = malloc(args->len * sizeof(type)); \
|
|
||||||
if (!args->array) return -1; \
|
|
||||||
} \
|
|
||||||
} else { \
|
|
||||||
args->len--; \
|
|
||||||
args->array[args->len - 1] = node->val; \
|
|
||||||
free(node); \
|
|
||||||
} \
|
|
||||||
return 0; \
|
|
||||||
} \
|
|
||||||
\
|
|
||||||
type* name##_to_array(name* str) { \
|
|
||||||
struct name##_array_args args = {.backwards = 0, .len = 1}; \
|
|
||||||
if (foreach_twopass_##name(str, _##name##_to_array, &args)) \
|
|
||||||
return NULL; \
|
|
||||||
return args.array; \
|
|
||||||
}
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
CREATE_LIST(string_array, char*)
|
int _findalias(alias_list_node* node, void* vargs) {
|
||||||
CREATE_TOARRAY(string_array, char*, %s)
|
alias_list_node** args = (alias_list_node**)vargs; // va a fare il casting del pointer
|
||||||
CREATE_LIST(string, char)
|
char *n, *a; // va a creare delle variabili temporanee
|
||||||
CREATE_TOARRAY(string, char, %c)
|
for (n = node->val.name, a = (*args)->val.name; *n && *a; n++, a++) {
|
||||||
|
if (*n != *a) return 0; // e a confrontarle
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*n ^ *a) return 0; // in case siamo arrivati alla fine dell'array invece di avere trovato il nome giusto
|
||||||
|
|
||||||
|
*args = node; // impostiamo gli argomenti al nodo con nome giusto
|
||||||
|
return 1; // ritorniamo 1 così da interrompere l'esecuzione del ciclo foreach
|
||||||
|
}
|
||||||
|
|
||||||
|
// funzione che va a trovare un alias
|
||||||
|
alias_list_node* findalias(char* name) {
|
||||||
|
struct alias val = {.name = name}; // va a creare un alias con nome come passato
|
||||||
|
alias_list_node node = {.val = val}; // va a creare un node
|
||||||
|
alias_list_node* ptr = &node; // e un pointer per contenerlo
|
||||||
|
if (foreach_alias_list(&aliases, *_findalias, &ptr)) return ptr; // se trovato viene ritornato
|
||||||
|
return NULL; // senno ritorna null
|
||||||
|
}
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
int last_exit = 0;
|
||||||
|
//}}}
|
||||||
|
|
||||||
|
// Builtin aliases {{{
|
||||||
|
|
||||||
|
// funzione exit come definita da POSIX
|
||||||
|
int __exit(char** args) {
|
||||||
|
if (args[1]) exit(atoi(args[1]));
|
||||||
|
exit(last_exit);
|
||||||
|
}
|
||||||
|
struct alias exit_alias = {
|
||||||
|
.name = "exit",
|
||||||
|
.fn = __exit,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ancora non implementata, out of scope
|
||||||
|
int set(char** args) {
|
||||||
|
if (args[1]) {
|
||||||
|
} else {
|
||||||
|
return printf(
|
||||||
|
"a: %b,\n"
|
||||||
|
"C: %b,\n"
|
||||||
|
"e: %b,\n"
|
||||||
|
"f: %b,\n"
|
||||||
|
"h: %b,\n"
|
||||||
|
"n: %b,\n"
|
||||||
|
"u: %b,\n"
|
||||||
|
"v: %b,\n"
|
||||||
|
"x: %b,\n",
|
||||||
|
optStatus.a, optStatus.C, optStatus.e, optStatus.f, optStatus.h,
|
||||||
|
optStatus.n, optStatus.u, optStatus.v, optStatus.x
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct alias set_alias = {
|
||||||
|
.name = "set",
|
||||||
|
.fn = set,
|
||||||
|
};
|
||||||
|
|
||||||
|
//}}}
|
||||||
|
|
||||||
|
// Variable type {{{
|
||||||
struct variable {
|
struct variable {
|
||||||
char* name;
|
char* name;
|
||||||
char* value;
|
char* value;
|
||||||
};
|
};
|
||||||
// clang-format off
|
|
||||||
|
// tipo non stampabile facilmente, rimossa temporanemante la funzione di log
|
||||||
#undef LOG
|
#undef LOG
|
||||||
#define LOG(...)
|
#define LOG(...)
|
||||||
CREATE_LIST(variable_list, struct variable)
|
CREATE_LIST(variable_list, struct variable)
|
||||||
CREATE_TOARRAY(variable_list, struct variable, '\0')
|
CREATE_TOARRAY(variable_list, struct variable, '\0')
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
#undef LOG
|
#undef LOG
|
||||||
#define LOG(...) printf(__VA_ARGS__)
|
#define LOG(...) \
|
||||||
|
do { \
|
||||||
|
printf(__VA_ARGS__); \
|
||||||
|
fflush(stdout); \
|
||||||
|
} while (0)
|
||||||
#endif
|
#endif
|
||||||
// clang-format on
|
|
||||||
variable_list variables = {.head = NULL};
|
variable_list variables = {.head = NULL};
|
||||||
|
|
||||||
const char* getorsetenv(const char* name, const char* val) {
|
const char* getorsetenv(const char* name, const char* val) {
|
||||||
|
|
@ -80,130 +225,263 @@ const char* getorsetenv(const char* name, const char* val) {
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// implementazione equivalente alla ricerca di alias
|
||||||
int _find_variable(variable_list_node* node, void* args) {
|
int _find_variable(variable_list_node* node, void* args) {
|
||||||
struct variable* arg = (struct variable*)args;
|
struct variable* arg = *((struct variable**)args);
|
||||||
|
LOG("checking %s against %s\n", node->val.name, arg->name);
|
||||||
if (strlen(node->val.name) == strlen(arg->name)) {
|
if (strlen(node->val.name) == strlen(arg->name)) {
|
||||||
for (int i = 0; node->val.name[i] && arg->name[i]; i++) {
|
for (int i = 0; node->val.name[i] && arg->name[i]; i++) {
|
||||||
if (node->val.name[i] != arg->name[i]) return 0;
|
if (node->val.name[i] != arg->name[i]) return 0;
|
||||||
}
|
}
|
||||||
arg->value = node->val.value;
|
*arg = node->val;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
char* find_variable(char* name) {
|
char* find_variable(char* name) {
|
||||||
struct variable ret = {.name = name};
|
LOG("searching for %s\n", name);
|
||||||
if (foreach_variable_list(&variables, _find_variable, &ret))
|
|
||||||
return ret.value;
|
|
||||||
if (!name) return NULL;
|
if (!name) return NULL;
|
||||||
|
struct variable tmp = {.name = name};
|
||||||
|
struct variable* ret = &tmp;
|
||||||
|
if (foreach_variable_list(&variables, _find_variable, &ret)) {
|
||||||
|
LOG("found %s", ret->value);
|
||||||
|
return ret->value;
|
||||||
|
}
|
||||||
|
LOG("falling back to getenv\n");
|
||||||
return getenv(name);
|
return getenv(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// come il find_variable ma senza fallback
|
||||||
|
char* find_local_variable(char* name) {
|
||||||
|
LOG("searching for local %s\n", name);
|
||||||
|
if (!name) return NULL;
|
||||||
|
struct variable tmp = {.name = name};
|
||||||
|
struct variable* ret = &tmp;
|
||||||
|
if (foreach_variable_list(&variables, _find_variable, &ret))
|
||||||
|
return ret->value;
|
||||||
|
LOG("not found\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// trova e rimpiazza una variabile
|
||||||
|
int find_and_replace_local_variable(char* name, char* replacement) {
|
||||||
|
LOG("searching for local %s\n", name);
|
||||||
|
if (!name) return 1;
|
||||||
|
struct variable tmp = {.name = name};
|
||||||
|
struct variable* ret = &tmp;
|
||||||
|
if (foreach_variable_list(&variables, _find_variable, &ret)) {
|
||||||
|
free(ret->value);
|
||||||
|
ret->value = replacement;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
LOG("not found\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
//}}}
|
||||||
|
|
||||||
_Bool is_subshell = 0;
|
_Bool is_subshell = 0;
|
||||||
|
|
||||||
|
// Text processing {{{
|
||||||
#define throw(ret, ...) \
|
#define throw(ret, ...) \
|
||||||
{ \
|
do { \
|
||||||
fprintf(stderr, __VA_ARGS__); \
|
fprintf(stderr, __VA_ARGS__); \
|
||||||
return ret; \
|
return ret; \
|
||||||
}
|
} while (0)
|
||||||
|
|
||||||
#define CHECK(var, val) var& val
|
#define CHECK(var, val) var& val
|
||||||
|
|
||||||
#define ESCAPE 0x1
|
#define ESCAPE 1
|
||||||
#define SET_ESCAPE situation ^= ESCAPE
|
#define SET_ESCAPE situation ^= ESCAPE
|
||||||
#define IS_ESCAPED CHECK(situation, ESCAPE)
|
#define IS_ESCAPED CHECK(situation, ESCAPE)
|
||||||
|
|
||||||
#define QUOTED 0x2
|
#define QUOTED 2
|
||||||
#define SET_QUOTED situation ^= QUOTED
|
#define SET_QUOTED situation ^= QUOTED
|
||||||
#define IS_QUOTED CHECK(situation, QUOTED)
|
#define IS_QUOTED CHECK(situation, QUOTED)
|
||||||
|
|
||||||
#define DOUBLE_QUOTED 0x4
|
#define DOUBLE_QUOTED 4
|
||||||
#define SET_DOUBLE_QUOTED situation ^= DOUBLE_QUOTED
|
#define SET_DOUBLE_QUOTED situation ^= DOUBLE_QUOTED
|
||||||
#define IS_DOUBLE_QUOTED CHECK(situation, DOUBLE_QUOTED)
|
#define IS_DOUBLE_QUOTED CHECK(situation, DOUBLE_QUOTED)
|
||||||
|
|
||||||
#define VAR 0x8
|
#define VAR 8
|
||||||
#define SET_VAR situation ^= VAR
|
#define SET_VAR situation ^= VAR
|
||||||
#define IS_VAR CHECK(situation, VAR)
|
#define IS_VAR CHECK(situation, VAR)
|
||||||
#define RESOLVE_VAR \
|
|
||||||
{ \
|
#define RESOLVE_VAR() \
|
||||||
char* varname = string_to_array(&var_buffer); \
|
do { \
|
||||||
char* content = find_variable(varname); \
|
append_string(&var_buffer, 0); /* si aggiunge un terminator */ \
|
||||||
if (content) { \
|
char* varname = string_to_array(&var_buffer); \
|
||||||
for (; *content; content++) { \
|
char* content = find_variable(varname); /* cerchiamo la variabile */ \
|
||||||
append_string(&buf, *content); \
|
if (content) { /* se la troviamo */ \
|
||||||
} \
|
for (; *content; content++) { \
|
||||||
} \
|
append_string( \
|
||||||
free(varname); \
|
&buf, *content \
|
||||||
SET_VAR; \
|
); /* aggiungiamo il suo valore al buffer */ \
|
||||||
}
|
} \
|
||||||
|
} \
|
||||||
|
free(varname); \
|
||||||
|
SET_VAR; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define DUMP_UNINTERRUPTED() \
|
||||||
|
do { \
|
||||||
|
append_string(&uninterrupted, 0); /* aggiungiamo un terminatore */ \
|
||||||
|
char* dump_tofree = string_to_array(&uninterrupted); \
|
||||||
|
LOG("dump_tofree: %s", dump_tofree); \
|
||||||
|
if (dump_tofree) { \
|
||||||
|
if (dump) { /* se siamo dopo un '=' */ \
|
||||||
|
LOG(", dump: %s\n", dump); \
|
||||||
|
struct variable tmpvar = {.name = dump, .value = dump_tofree}; \
|
||||||
|
append_variable_list( \
|
||||||
|
&variables, tmpvar \
|
||||||
|
); /* aggiungiamo la variabile */ \
|
||||||
|
dump = NULL; \
|
||||||
|
} else { \
|
||||||
|
LOG("\n"); \
|
||||||
|
for (char* tmp = dump_tofree; *dump; \
|
||||||
|
dump++) /* aggiungiamo al buffer normale */ \
|
||||||
|
append_string(&buf, *tmp); \
|
||||||
|
free(dump_tofree); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
char* resolve(const char* str) {
|
char* resolve(const char* str) {
|
||||||
char situation = 0;
|
char situation = 0;
|
||||||
|
|
||||||
string buf = {.head = NULL};
|
string buf = {.head = NULL};
|
||||||
|
char* dump = NULL;
|
||||||
|
|
||||||
string var_buffer = {.head = NULL};
|
string var_buffer = {.head = NULL};
|
||||||
|
string uninterrupted = {.head = NULL};
|
||||||
|
|
||||||
|
// switch case di ogni carattere speciale definito da posix supportato
|
||||||
for (const char* current = str; *current; current++) {
|
for (const char* current = str; *current; current++) {
|
||||||
LOG("situation_pre: %i, current: %p = %c", situation, current,
|
LOG("situation_pre: %i, current: %p = %c", situation, current,
|
||||||
*current);
|
*current);
|
||||||
switch (*current) {
|
switch (*current) {
|
||||||
|
case '#':
|
||||||
|
if (IS_VAR)
|
||||||
|
RESOLVE_VAR();
|
||||||
|
else
|
||||||
|
DUMP_UNINTERRUPTED();
|
||||||
|
if (IS_QUOTED) {
|
||||||
|
append_string(&buf, '#');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (IS_DOUBLE_QUOTED) {
|
||||||
|
append_string(&buf, '#');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (IS_ESCAPED) {
|
||||||
|
append_string(&buf, '#');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
goto FIN;
|
||||||
case '\\':
|
case '\\':
|
||||||
|
if (IS_VAR)
|
||||||
|
RESOLVE_VAR();
|
||||||
|
else
|
||||||
|
DUMP_UNINTERRUPTED();
|
||||||
if (IS_QUOTED) {
|
if (IS_QUOTED) {
|
||||||
append_string(&buf, '\\');
|
append_string(&buf, '\\');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (IS_VAR)
|
if (IS_DOUBLE_QUOTED) {
|
||||||
SET_VAR;
|
switch (*(++current)) {
|
||||||
else if (IS_ESCAPED)
|
case '$':
|
||||||
|
case '`':
|
||||||
|
case '"':
|
||||||
|
case '\\':
|
||||||
|
append_string(&buf, *current);
|
||||||
|
default:
|
||||||
|
append_string(&buf, '\\');
|
||||||
|
current--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else if (IS_ESCAPED)
|
||||||
append_string(&buf, '\\');
|
append_string(&buf, '\\');
|
||||||
SET_ESCAPE;
|
SET_ESCAPE;
|
||||||
goto END;
|
goto END;
|
||||||
|
|
||||||
case '\'':
|
case '\'':
|
||||||
if (IS_VAR) SET_VAR;
|
|
||||||
if (IS_QUOTED && IS_ESCAPED)
|
if (IS_QUOTED && IS_ESCAPED)
|
||||||
throw(
|
throw(
|
||||||
NULL,
|
NULL,
|
||||||
"attempt to escape a \"'\" while inside quotes, this "
|
"attempt to escape a \"'\" while inside quotes, this "
|
||||||
"goes against posix standard"
|
"goes against posix standard"
|
||||||
);
|
);
|
||||||
|
if (IS_VAR)
|
||||||
|
RESOLVE_VAR();
|
||||||
|
else
|
||||||
|
DUMP_UNINTERRUPTED();
|
||||||
SET_QUOTED;
|
SET_QUOTED;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '"':
|
case '"':
|
||||||
if (IS_VAR) SET_VAR;
|
if (IS_VAR)
|
||||||
|
RESOLVE_VAR();
|
||||||
|
else
|
||||||
|
DUMP_UNINTERRUPTED();
|
||||||
if (IS_ESCAPED || IS_QUOTED) append_string(&buf, '"');
|
if (IS_ESCAPED || IS_QUOTED) append_string(&buf, '"');
|
||||||
SET_DOUBLE_QUOTED;
|
SET_DOUBLE_QUOTED;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '$':
|
case '$':
|
||||||
|
DUMP_UNINTERRUPTED();
|
||||||
if (IS_ESCAPED || IS_QUOTED)
|
if (IS_ESCAPED || IS_QUOTED)
|
||||||
append_string(&buf, '$');
|
append_string(&buf, '$');
|
||||||
else if (IS_VAR) {
|
else if (IS_VAR) {
|
||||||
if (*(current - 1) == '$') {
|
if (*(current - 1) == '$') {
|
||||||
for (pid_t id = (!is_subshell) ? getpid() : getppid();
|
for (pid_t id = (!is_subshell)
|
||||||
|
? getpid()
|
||||||
|
: getppid(); // non ancora in uso
|
||||||
id > 0; id = id / 10)
|
id > 0; id = id / 10)
|
||||||
append_string(&buf, (id % 10) + 48);
|
append_string(&buf, (id % 10) + 48);
|
||||||
} else {
|
} else {
|
||||||
RESOLVE_VAR;
|
RESOLVE_VAR();
|
||||||
SET_VAR;
|
SET_VAR;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
SET_VAR;
|
SET_VAR;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case '=':
|
||||||
|
if (IS_QUOTED || IS_DOUBLE_QUOTED || IS_ESCAPED) {
|
||||||
|
if (IS_VAR) RESOLVE_VAR();
|
||||||
|
append_string(&buf, *current);
|
||||||
|
} else {
|
||||||
|
dump = string_to_array(&uninterrupted);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case '\n':
|
case '\n':
|
||||||
if (IS_ESCAPED) break;
|
if (IS_ESCAPED) break;
|
||||||
|
|
||||||
case ' ':
|
case ' ':
|
||||||
case ';':
|
case ';':
|
||||||
case ',':
|
case ',':
|
||||||
case '.':
|
case '.':
|
||||||
if (IS_VAR) RESOLVE_VAR;
|
|
||||||
default:
|
|
||||||
if (IS_VAR)
|
if (IS_VAR)
|
||||||
append_string(&var_buffer, *current);
|
RESOLVE_VAR();
|
||||||
else
|
else
|
||||||
|
DUMP_UNINTERRUPTED();
|
||||||
|
append_string(&buf, *current);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if ((*current >= 'A' && *current <= 'Z') ||
|
||||||
|
(*current >= 'a' && *current <= 'z') ||
|
||||||
|
(*current >= '0' && *current <= '9'))
|
||||||
|
|
||||||
|
if (IS_VAR)
|
||||||
|
append_string(&var_buffer, *current);
|
||||||
|
else
|
||||||
|
append_string(&uninterrupted, *current);
|
||||||
|
else {
|
||||||
|
if (IS_VAR)
|
||||||
|
SET_VAR;
|
||||||
|
else
|
||||||
|
DUMP_UNINTERRUPTED();
|
||||||
append_string(&buf, *current);
|
append_string(&buf, *current);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (IS_ESCAPED) SET_ESCAPE;
|
if (IS_ESCAPED) SET_ESCAPE;
|
||||||
END:
|
END:
|
||||||
|
|
@ -211,7 +489,9 @@ char* resolve(const char* str) {
|
||||||
LOG_WRAP(print_string, &buf, "'%c', ");
|
LOG_WRAP(print_string, &buf, "'%c', ");
|
||||||
LOG(" }, situation: %i\n", situation);
|
LOG(" }, situation: %i\n", situation);
|
||||||
}
|
}
|
||||||
if (IS_VAR) RESOLVE_VAR;
|
if (IS_VAR) RESOLVE_VAR();
|
||||||
|
FIN:
|
||||||
|
DUMP_UNINTERRUPTED();
|
||||||
append_string(&buf, 0);
|
append_string(&buf, 0);
|
||||||
LOG("string: { ");
|
LOG("string: { ");
|
||||||
LOG_WRAP(print_string, &buf, "'%c', ");
|
LOG_WRAP(print_string, &buf, "'%c', ");
|
||||||
|
|
@ -220,42 +500,150 @@ char* resolve(const char* str) {
|
||||||
LOG("parsed_internal: \"%s\"\n", parsed);
|
LOG("parsed_internal: \"%s\"\n", parsed);
|
||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
//}}}
|
||||||
|
|
||||||
int prompt() {
|
// Io {{{
|
||||||
return fprintf(stderr, "%s", resolve(getorsetenv("PS1", "\\$ ")));
|
// print della prompt inpostand anche la variabile se non ancora impostata
|
||||||
}
|
int prompt() { return fprintf(stderr, "%s", getorsetenv("PS1", "$ ")); }
|
||||||
|
|
||||||
|
// semplice funzione di imput
|
||||||
|
FILE* inputFile;
|
||||||
char* input() {
|
char* input() {
|
||||||
char* line = NULL;
|
char* line = NULL;
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
if (getline(&line, &n, stdin) < 0 && ferror(stdin)) {
|
if (getline(&line, &n, inputFile) <= 0) {
|
||||||
free(line);
|
free(line);
|
||||||
return NULL;
|
return NULL;
|
||||||
};
|
};
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
|
|
||||||
int exec(char* line) {
|
// clear dell'imput quando viene premuto in interactive mod
|
||||||
LOG("line: %s\n", line);
|
void clearInput(int sig) {
|
||||||
|
signal(sig, SIG_IGN);
|
||||||
|
LOG_WRAP(char c; do {} while, 0);
|
||||||
|
fseek(stdin, 0, SEEK_END);
|
||||||
|
fprintf(stderr, "\033[2K");
|
||||||
|
fprintf(stderr, "\r");
|
||||||
|
prompt();
|
||||||
|
signal(sig, clearInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
//}}}
|
||||||
|
|
||||||
|
// Execute {{{
|
||||||
|
|
||||||
|
// whitespace checker
|
||||||
|
_Bool isjustwhite(char* str) {
|
||||||
|
for (; *str; str++)
|
||||||
|
if (*str != ' ' && *str != '\n') return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _exec(char* line);
|
||||||
|
int __exec(char** argv, char* command, char* fromalias);
|
||||||
|
// exec() = __exec in un caso e _exec nell'altro
|
||||||
|
#define FIRST(X, ...) X
|
||||||
|
#define exec(...) \
|
||||||
|
_Generic(FIRST(__VA_ARGS__), char**: __exec, default: _exec)(__VA_ARGS__)
|
||||||
|
|
||||||
|
int __exec(char** argv, char* command, char* fromalias) {
|
||||||
|
if (fromalias) {
|
||||||
|
for (char *c = command, *f = fromalias; *c && *f; c++, f++) {
|
||||||
|
if (*c != *f) {
|
||||||
|
goto ALIAS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
goto SKIP;
|
||||||
|
}
|
||||||
|
// se dobbiamo trovare l'alias
|
||||||
|
ALIAS:
|
||||||
|
alias_list_node* node = findalias(command);
|
||||||
|
if (!node) goto SKIP;
|
||||||
|
|
||||||
|
if (node->val.fn) {
|
||||||
|
return (*node->val.fn)(argv); // se troviamo un builtin lo eseguiamo
|
||||||
|
}
|
||||||
|
|
||||||
|
// troviamo lunghezza degli argomenti
|
||||||
|
int argc;
|
||||||
|
for (argc = 0; (node->val.args)[argc++];);
|
||||||
|
char** new_argv = malloc(argc);
|
||||||
|
// li spostiamo tutti nella nuova argv
|
||||||
|
memcpy(new_argv, argv, argc);
|
||||||
|
|
||||||
|
// rifacciamo lo stesso con gli argomenti passati nella cli
|
||||||
|
int old_argc;
|
||||||
|
for (old_argc = -1; argv[++old_argc];);
|
||||||
|
new_argv = realloc(new_argv, argc + old_argc);
|
||||||
|
memcpy(new_argv + argc, argv + 1, old_argc);
|
||||||
|
return exec(new_argv, node->val.command, command);
|
||||||
|
|
||||||
|
SKIP:
|
||||||
|
LOG("\n");
|
||||||
|
if (isjustwhite(command)) return 0; // in caso sia solo \n e ' '
|
||||||
|
if (optStatus.x) {
|
||||||
|
// print dei commandi
|
||||||
|
printf("%s ", command);
|
||||||
|
for (char** tmp = argv; *tmp; tmp++) {
|
||||||
|
printf("%s ", *tmp);
|
||||||
|
};
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// vfork del processo
|
||||||
|
pid_t proc = vfork();
|
||||||
|
|
||||||
|
// se non ѐ andato a buon fine ritorna errore
|
||||||
|
if (proc == -1) return -1;
|
||||||
|
|
||||||
|
if (proc) {
|
||||||
|
// se siamo il parent
|
||||||
|
siginfo_t infop;
|
||||||
|
int ret = waitid(
|
||||||
|
P_PID, proc, &infop, WEXITED | WSTOPPED
|
||||||
|
); /* "aspettiamo" il processo (solo per ottenere il return, in realtà
|
||||||
|
vfork aspetta di default) */
|
||||||
|
if (ret) return -1; // se non ci riusciamo, errore
|
||||||
|
last_exit = infop.si_status;
|
||||||
|
if (infop.si_status)
|
||||||
|
fprintf(
|
||||||
|
stderr, "Process returned with error code %i\n", infop.si_status
|
||||||
|
);
|
||||||
|
LOG("\n\n");
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
// se siamo il child process
|
||||||
|
int ret = execvp(command, argv); // execvp
|
||||||
|
if (ret == -1)
|
||||||
|
fprintf(stderr, "errno: %i\n", errno); // se non ha funzionato
|
||||||
|
_exit(ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// separazione di argouments e command
|
||||||
|
void split(char* line, char*** strargs_ret, char** orig_ret) {
|
||||||
_Bool todo = 1;
|
_Bool todo = 1;
|
||||||
string_array args = {.head = NULL};
|
string_array args = {.head = NULL};
|
||||||
string arg = {.head = NULL};
|
string arg = {.head = NULL};
|
||||||
char* orig = line;
|
char* orig = line;
|
||||||
for (; *line != '\n'; line++) {
|
for (; *line != '\n' && *line; line++) {
|
||||||
LOG("line: %c, \n", *line);
|
LOG("main.split.for: line: %c, ", *line);
|
||||||
if (*line == ' ') {
|
if (*line == ' ') {
|
||||||
if (todo) {
|
if (todo) {
|
||||||
orig[line - orig] = 0;
|
orig[line - orig] = 0;
|
||||||
todo = 0;
|
todo = 0;
|
||||||
} else {
|
} else {
|
||||||
|
LOG_WRAP(print_string, &arg, "main.split.for.arg: %c\n");
|
||||||
append_string(&arg, 0);
|
append_string(&arg, 0);
|
||||||
append_string_array(&args, string_to_array(&arg));
|
append_string_array(&args, string_to_array(&arg));
|
||||||
}
|
}
|
||||||
} else if (!todo) {
|
} else if (!todo) {
|
||||||
|
LOG("appending; \n");
|
||||||
append_string(&arg, *line);
|
append_string(&arg, *line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG("line: %c, \n", *line);
|
LOG("main.split: line: %c, ", *line);
|
||||||
if (todo) {
|
if (todo) {
|
||||||
orig[line - orig] = 0;
|
orig[line - orig] = 0;
|
||||||
todo = 0;
|
todo = 0;
|
||||||
|
|
@ -267,52 +655,211 @@ int exec(char* line) {
|
||||||
append_string_array(&args, NULL);
|
append_string_array(&args, NULL);
|
||||||
LOG("args: { ");
|
LOG("args: { ");
|
||||||
LOG_WRAP(print_string_array, &args, "%s, ");
|
LOG_WRAP(print_string_array, &args, "%s, ");
|
||||||
LOG(" }");
|
LOG(" }\n");
|
||||||
char** strargs = string_array_to_array(&args);
|
char** strargs = string_array_to_array(&args);
|
||||||
for (int i = 0; strargs[i]; i++) {
|
for (int i = 0; strargs[i]; i++) {
|
||||||
LOG("strargs[%i] %s", i, strargs[i]);
|
LOG("strargs[%i] %s\n", i, strargs[i]);
|
||||||
}
|
}
|
||||||
pid_t proc = vfork();
|
|
||||||
if (proc == -1) return -1;
|
|
||||||
if (proc) {
|
|
||||||
siginfo_t infop;
|
|
||||||
int ret = waitid(P_PID, proc, &infop, WEXITED | WSTOPPED);
|
|
||||||
if (ret) return -1;
|
|
||||||
if (infop.si_status)
|
|
||||||
fprintf(
|
|
||||||
stderr, "Process returned with error code %i\n", infop.si_status
|
|
||||||
);
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
int ret = execvp(orig, strargs);
|
|
||||||
if (ret == -1) fprintf(stderr, "errno: %i\n", errno);
|
|
||||||
_exit(ret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
*strargs_ret = strargs;
|
||||||
LOG("sizeof(string_node): %i, sizeof(string): %i, "
|
*orig_ret = orig;
|
||||||
"sizeof(string_array_args): %i\n",
|
}
|
||||||
sizeof(string_node), sizeof(string), sizeof(struct string_array_args));
|
int _exec(char* line) {
|
||||||
LOG("sizeof(string_array_node): %i, sizeof(string_array): %i, "
|
LOG("line: %s\n", line);
|
||||||
"sizeof(string_array_array_args): %i\n",
|
char **strargs, *orig;
|
||||||
sizeof(string_array_node), sizeof(string_array),
|
split(line, &strargs, &orig);
|
||||||
sizeof(struct string_array_array_args));
|
return exec(strargs, orig, NULL);
|
||||||
LOG("sizeof(variable): %i, sizeof(variable_list): %i, "
|
}
|
||||||
"sizeof(variable_list_node): %i, sizeof(variable_list_array_args): "
|
//}}}
|
||||||
"%i\n",
|
|
||||||
sizeof(struct variable), sizeof(variable_list),
|
// Enums {{{
|
||||||
sizeof(variable_list_node), sizeof(struct variable_list_array_args));
|
|
||||||
LOG("test, resolve \"\\$\", \"%s\"\n", resolve("\\$ "));
|
typedef enum {
|
||||||
for (;;) {
|
File,
|
||||||
prompt();
|
FakeFile,
|
||||||
char* line = input();
|
Stdin,
|
||||||
LOG("input: %s", line);
|
} Mode;
|
||||||
if (!line) return errno;
|
|
||||||
char* parsed = resolve(line);
|
typedef enum {
|
||||||
LOG("parsed: %s", parsed);
|
Normal,
|
||||||
if (!parsed) return -1;
|
ScriptArgs,
|
||||||
int ret = exec(parsed);
|
} ParseMode;
|
||||||
if (ret) return ret;
|
|
||||||
}
|
// }}}
|
||||||
|
|
||||||
|
// Convarsions {{{
|
||||||
|
char* itoa(int integer) {
|
||||||
|
if (!integer) return "0";
|
||||||
|
string tmp;
|
||||||
|
for (; integer; integer /= 10) append_string(&tmp, integer % 10 + '0');
|
||||||
|
return string_to_array(&tmp);
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
_Bool interactive = false;
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
LOG("set arg0 to %s\n", argv[0]);
|
||||||
|
struct variable arg0 = {.name = "0", .value = argv[0]};
|
||||||
|
append_variable_list(&variables, arg0);
|
||||||
|
inputFile = stdin;
|
||||||
|
Mode mode = Stdin;
|
||||||
|
ParseMode parsemode = Normal;
|
||||||
|
|
||||||
|
_Bool c = false;
|
||||||
|
_Bool s = false;
|
||||||
|
|
||||||
|
// Initialize builtins {{{
|
||||||
|
|
||||||
|
append_alias_list(&aliases, exit_alias);
|
||||||
|
append_alias_list(&aliases, set_alias);
|
||||||
|
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// Handle argouments {{{
|
||||||
|
if (argc - 1) {
|
||||||
|
int i;
|
||||||
|
for (i = 1; i < argc && parsemode == Normal; i++) {
|
||||||
|
LOG("i: %i, argv[i]: %s\n", i, argv[i]);
|
||||||
|
switch (*(argv[i])) {
|
||||||
|
case '-':
|
||||||
|
char c;
|
||||||
|
if ((c = argv[i][1])) {
|
||||||
|
switch (c) {
|
||||||
|
case 'c':
|
||||||
|
if (s)
|
||||||
|
throw(
|
||||||
|
1,
|
||||||
|
"only specify either -s or -c, not both"
|
||||||
|
);
|
||||||
|
c = true;
|
||||||
|
++i;
|
||||||
|
LOG("i: %i, argv[i](command): %s\n", i,
|
||||||
|
argv[i]);
|
||||||
|
inputFile =
|
||||||
|
fmemopen(argv[i], strlen(argv[i]), "r");
|
||||||
|
if (!inputFile)
|
||||||
|
throw(
|
||||||
|
errno,
|
||||||
|
"failed to execute commands provided "
|
||||||
|
"by -c"
|
||||||
|
);
|
||||||
|
mode = FakeFile;
|
||||||
|
parsemode = ScriptArgs;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
if (c)
|
||||||
|
throw(
|
||||||
|
1,
|
||||||
|
"only specify either -s or -c, not both"
|
||||||
|
);
|
||||||
|
s = true;
|
||||||
|
break;
|
||||||
|
case 'i':
|
||||||
|
interactive = true;
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
optStatus.a = true;
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
optStatus.n = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parsemode = ScriptArgs;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
struct stat info = {0};
|
||||||
|
if (stat(argv[i], &info)) return errno;
|
||||||
|
if ((info.st_mode & S_IXUSR /*owner has execution*/ &&
|
||||||
|
info.st_uid == getuid()) ||
|
||||||
|
(info.st_mode & S_IXGRP /*group has execution*/ &&
|
||||||
|
info.st_gid == getgid()) ||
|
||||||
|
(info.st_mode & S_IXOTH /*other has execution*/)) {
|
||||||
|
inputFile = fopen(argv[i], "r");
|
||||||
|
if (!inputFile)
|
||||||
|
throw(errno, "failed to open file provided");
|
||||||
|
} else
|
||||||
|
throw(1, "file specified is not executable");
|
||||||
|
mode = File;
|
||||||
|
parsemode = ScriptArgs;
|
||||||
|
find_and_replace_local_variable("0", argv[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i < argc && mode == Stdin) {
|
||||||
|
struct stat info = {0};
|
||||||
|
if (stat(argv[i], &info)) return errno;
|
||||||
|
if ((info.st_mode & S_IXUSR /*owner has execution*/ &&
|
||||||
|
info.st_uid == getuid()) ||
|
||||||
|
(info.st_mode & S_IXGRP /*group has execution*/ &&
|
||||||
|
info.st_gid == getgid()) ||
|
||||||
|
(info.st_mode & S_IXOTH /*other has execution*/)) {
|
||||||
|
inputFile = fopen(argv[i], "r");
|
||||||
|
if (!inputFile) throw(errno, "failed to open file provided");
|
||||||
|
}
|
||||||
|
mode = File;
|
||||||
|
find_and_replace_local_variable("0", argv[i]);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
for (int j = 1; i < argc; i++, j++) {
|
||||||
|
struct variable argn = {.name = itoa(j), .value = argv[i]};
|
||||||
|
append_variable_list(&variables, argn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getgid() != getegid() || getuid() != geteuid())
|
||||||
|
interactive = false; // come definito da posix
|
||||||
|
if (interactive) {
|
||||||
|
do {
|
||||||
|
char* filename = getenv("ENV");
|
||||||
|
if (!filename) break;
|
||||||
|
FILE* backupfile = inputFile;
|
||||||
|
|
||||||
|
char* line = input();
|
||||||
|
LOG("input: %s\n", line);
|
||||||
|
if (!line) {
|
||||||
|
if (feof(inputFile))
|
||||||
|
__exit(NULL);
|
||||||
|
else
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
char* parsed = resolve(line);
|
||||||
|
LOG("parsed: %s\n", parsed);
|
||||||
|
if (!parsed) return -1;
|
||||||
|
int ret = exec(parsed);
|
||||||
|
if (ret) return ret;
|
||||||
|
inputFile = backupfile;
|
||||||
|
} while (0);
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
if (interactive) {
|
||||||
|
signal(SIGINT, clearInput);
|
||||||
|
signal(SIGQUIT, SIG_IGN);
|
||||||
|
signal(SIGTERM, SIG_IGN);
|
||||||
|
};
|
||||||
|
signal(SIGTTIN, SIG_IGN);
|
||||||
|
signal(SIGTTOU, SIG_IGN);
|
||||||
|
signal(SIGTSTP, SIG_IGN);
|
||||||
|
|
||||||
|
// Main loop {{{
|
||||||
|
for (;;) {
|
||||||
|
if (mode == Stdin) prompt();
|
||||||
|
char* line = input();
|
||||||
|
LOG("input: %s\n", line);
|
||||||
|
if (!line) {
|
||||||
|
if (feof(inputFile))
|
||||||
|
__exit(NULL);
|
||||||
|
else
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
char* parsed = resolve(line);
|
||||||
|
LOG("parsed: %s\n", parsed);
|
||||||
|
if (!parsed) return -1;
|
||||||
|
if (interactive || !optStatus.n) {
|
||||||
|
int ret = exec(parsed);
|
||||||
|
if (ret) return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue