myadm

Simple MySQL client for the terminal
git clone git://git.bitsmanent.org/myadm
Log | Files | Refs | README | LICENSE

commit 5d2e31c70e54d80ffd6e344bc77a88a4a09865dd
parent 79f02bed6cd9ff5947ed71294abf2a5472fbc124
Author: Claudio Alessi <smoppy@gmail.com>
Date:   Tue,  3 May 2016 20:32:46 +0200

Refactoring records editing.

The big change is the ui_sql_edit_exec() routine which is reusable and allow to
edit a file, then exec the content into MySQL (if modified). This comes with a
bit of refactoring. In case of wrong SQL code the current file may be
re-opened. Also flow control chars are now escaped so that they are not
interpreted by MySQL.

This commit is a significant step further in order to write routines like, for
example, edittable() in a very simple manner. Though, it raises a strong need
to cleanup the code.

Diffstat:
Mmyadm.c | 212++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
1 file changed, 129 insertions(+), 83 deletions(-)

diff --git a/myadm.c b/myadm.c @@ -100,15 +100,17 @@ void detachfield(Field *f, Field **ff); void detachitem(Item *i, Item **ii); void die(const char *errstr, ...); void *ecalloc(size_t nmemb, size_t size); -char *editbuf(char *in, int len, int *sz); +void editfile(char *file); void editrecord(const Arg *arg); -char *escape(char *s, char c, int *nc); +int escape(char *esc, char *s, int sz, int c); +int escape_flowctrls(char *esc, char *s, int sz); char *fget(char *fn, int *sz); int fput(char *fn, char *s, int size); Item *getitem(int pos); int *getmaxlengths(Item *items, Field *fields); void itemsel(const Arg *arg); char *mksql_update_record(Item *item, Field *fields, char *tbl, char *pk); +MYSQL_RES *mysql_file_exec(char *file); MYSQL_RES *mysql_exec(const char *sqlstr, ...); int mysql_fields(MYSQL_RES *res, Field **fields); void mysql_fillview(MYSQL_RES *res, int showfds); @@ -131,6 +133,7 @@ void ui_refresh(void); void ui_set(const char *key, const char *fmtstr, ...); void ui_showfields(Field *fds, int *lens); void ui_showitems(Item *items, int *lens); +void ui_sql_edit_exec(char *sql); void usage(void); void viewdb(const Arg *arg); void viewdb_show(void); @@ -286,20 +289,10 @@ ecalloc(size_t nmemb, size_t size) { return p; } -char * -editbuf(char *in, int len, int *sz) { +void +editfile(char *file) { pid_t pid; - char tmp[] = "myadm.XXXXXX", *buf; - int fd, rc = -1; - - fd = mkstemp(tmp); - if(fd == -1) - return NULL; - close(fd); - if(fput(tmp, in, len) == -1) { - unlink(tmp); - return NULL; - } + int rc = -1; ui_end(); /* endwin() */ @@ -311,91 +304,78 @@ editbuf(char *in, int len, int *sz) { sigaction(SIGWINCH, &sa, NULL); if((pid = fork()) == 0) { - execl("/bin/sh", "sh", "-c", "$EDITOR \"$0\"", tmp, NULL); + execl("/bin/sh", "sh", "-c", "$EDITOR \"$0\"", file, NULL); _exit(127); } - else if(pid != -1) { - while(!WIFEXITED(rc)) - waitpid(pid, &rc, 0); - } + else if(pid == -1) + return; + while(!WIFEXITED(rc)) + waitpid(pid, &rc, 0); ui_init(); /* restore ncurses signal handlers */ + /* XXX restore above signal handlers? */ ui_redraw(); - - buf = fget(tmp, sz); - unlink(tmp); - return buf; } void editrecord(const Arg *arg) { Item *item = getitem(0); - char *tbl = selview->choice->cols[0], pk[MYSQLIDLEN+1], *buf, *sql; - int bufsz, sqlsz; + char *tbl = selview->choice->cols[0], pk[MYSQLIDLEN+1], *sql; if(!item) { ui_set("status", "No item selected."); return; } if(mysql_pkey(pk, tbl)) { - ui_set("status", "Cannot edit records in `%s`.", tbl); + ui_set("status", "Cannot edit records in `%s`, no unique key found.", tbl); return; } sql = mksql_update_record(item, selview->fields, tbl, pk); - sqlsz = strlen(sql); - buf = editbuf(sql, sqlsz, &bufsz); + ui_sql_edit_exec(sql); free(sql); - if(!buf) { - ui_set("status", "Cannot edit the record."); - return; - } - if(bufsz == sqlsz) { - ui_set("status", "No changes."); - } - else { - mysql_exec(buf); - if(*mysql_error(mysql)) - ui_set("status", "Wrong SQL code."); - else - reload(NULL); - } - free(buf); } -char * -escape(char *s, char c, int *nc) { - int len, n, i; - char *esc; - - *nc = 0; - for(esc = s; *esc; ++esc) - if(*esc == c) - ++*nc; - if(!*nc) - return s; - len = strlen(s) + *nc; - esc = ecalloc(1, len); - for(i = 0, n = 0; i < len; ++i) { +int +escape(char *esc, char *s, int sz, int c) { + int i, ei = 0; + + for(i = 0; i < sz; ++i) { if(s[i] == c) - esc[n++] = '\\'; - esc[n++] = s[i]; + esc[ei++] = '\\'; + esc[ei++] = s[i]; } - return esc; + esc[ei] = '\0'; + return ei; +} + +int +escape_flowctrls(char *esc, char *s, int sz) { + int i, ei = 0; + + for(i = 0; i < sz; ++i) { + if(s[i] == '\\' && s[i+1] != '\'') + esc[ei++] = '\\'; + esc[ei++] = s[i]; + } + esc[ei] = '\0'; + return ei; } char * fget(char *fn, int *sz) { - int fd; + int fd, size; char *buf; fd = open(fn, O_RDONLY); if(fd == -1) return NULL; - *sz = lseek(fd, 0, SEEK_END); + size = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); - buf = ecalloc(1, *sz+1); - read(fd, buf, *sz); - buf[*sz] = '\0'; + buf = ecalloc(1, size+1); + read(fd, buf, size); + buf[size] = '\0'; close(fd); + if(sz) + *sz = size; return buf; } @@ -457,7 +437,6 @@ getmaxlengths(Item *items, Field *fields) { void itemsel(const Arg *arg) { int pos; - char tmp[8]; if(!selview) return; @@ -467,8 +446,7 @@ itemsel(const Arg *arg) { pos = 0; else if(pos >= selview->nitems) pos = selview->nitems - 1; - snprintf(tmp, sizeof tmp, "%d", pos); - ui_set("pos", tmp); + ui_set("pos", "%d", pos); selview->cur = pos; } @@ -477,23 +455,20 @@ mksql_update_record(Item *item, Field *fields, char *tbl, char *pk) { Field *fld; char *sql, *col, *sqlfds = NULL, *pkv = NULL; size_t i, len = 0, cnt = 0, size = 0; - int nq; for(i = 0, fld = fields; fld; fld = fld->next, ++i) { if(!pkv && !strncmp(pk, fld->name, fld->len)) pkv = item->cols[i]; - len = 10 + fld->len + item->lens[i]; - col = escape(item->cols[i], '\'', &nq); - if(nq) - len += nq; + len = 10 + fld->len; + col = ecalloc(1, item->lens[i]*2+1); + len += escape(col, item->cols[i], item->lens[i], '\''); if(cnt + len >= size) if(!(sqlfds = realloc(sqlfds, (size += (len <= BUFSIZ ? BUFSIZ : len))))) die("cannot realloc %u bytes:", size); snprintf(&sqlfds[cnt], len, "\n%c`%s` = '%s'", cnt ? ',' : ' ', fld->name, col); cnt += len - 1; - if(nq) - free(col); + free(col); } size = 1 + 28 + cnt + strlen(pk) + strlen(pkv) + strlen(tbl); sql = ecalloc(1, size); @@ -504,7 +479,6 @@ mksql_update_record(Item *item, Field *fields, char *tbl, char *pk) { MYSQL_RES * mysql_exec(const char *sqlstr, ...) { - MYSQL_RES *res; va_list ap; char *sql; int sqlen; @@ -517,10 +491,7 @@ mysql_exec(const char *sqlstr, ...) { return NULL; } free(sql); - res = mysql_store_result(mysql); - if(!res) - return NULL; - return res; + return mysql_store_result(mysql); } int @@ -542,6 +513,36 @@ mysql_fields(MYSQL_RES *res, Field **fields) { return nfds; } +MYSQL_RES * +mysql_file_exec(char *file) { + MYSQL_RES *res; + char *buf, *esc; + int fd, size; + + fd = open(file, O_RDONLY); + if(fd == -1) + return NULL; + + lseek(fd, 0, SEEK_SET); + size = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + buf = ecalloc(1, size+1); + if(read(fd, buf, size) != size) { + free(buf); + return NULL; + } + buf[size] = '\0'; + + /* We do not want flow control chars to be interpreted. */ + esc = ecalloc(1, size*2+1); + escape_flowctrls(esc, buf, size); + res = mysql_exec(esc); + free(buf); + free(esc); + + return res; +} + void mysql_fillview(MYSQL_RES *res, int showfds) { cleanupitems(&selview->items); @@ -646,6 +647,51 @@ ui_showitems(Item *items, int *lens) { ui_set("pos", 0); } +void +ui_sql_edit_exec(char *sql) { + MYSQL_RES *res; + struct stat sb, sa; + int fd; + char tmpf[] = "/tmp/myadm.XXXXXX", *yn = "yn"; + + fd = mkstemp(tmpf); + if(fd == -1) { + ui_set("status", "Cannot make a temporary file."); + return; + } + if(write(fd, sql, strlen(sql)) == -1) { + close(fd); + unlink(tmpf); + ui_set("status", "Cannot write into the temporary file."); + return; + } + close(fd); + + while(1) { + stat(tmpf, &sb); + editfile(tmpf); + stat(tmpf, &sa); + if(!sa.st_size || sb.st_mtime == sa.st_mtime) { + ui_set("status", "No changes."); + break; + } + res = mysql_file_exec(tmpf); + if(*mysql_error(mysql)) { + ui_set("status", "Wrong SQL code."); + if(ui_ask("Continue editing ([y]/n)?", yn) == yn[0]) + continue; + } + /* We're expecting an update here which don't allocate any + * result but since the user is able to write any SQL code into + * the file, make sure to free any eventual record set. */ + mysql_free_result(res); + reload(NULL); + ui_set("status", "Updated."); + break; + } + unlink(tmpf); +} + View * newaview(const char *name, void (*func)(void)) { View *v; @@ -664,10 +710,10 @@ newaview(const char *name, void (*func)(void)) { * 0 never ask */ void quit(const Arg *arg) { - char *opts = "yn"; + char *yn= "yn"; if(arg->i) - if(ui_ask("Do you want to quit ([y]/n)?", opts) != opts[0]) + if(ui_ask("Do you want to quit ([y]/n)?", yn) != yn[0]) return; running = 0; }