#include #include #include #include #include #include #include #include #define DATABASE_PATH (FTAG_ROOT "/ftag.sqlite3") struct ftag_command { const char *name; void (*func)(int argc, char **argv); }; /* Parse arguments using an array of available commands. ARGV[0] has to match * the "name" field of an entry of COMMANDS. */ static void parse_args(int argc, char ** argv, const struct ftag_command *commands, int command_count); static void __sqlite3_check(int rc, sqlite3 *db, const char *file, int line, const char *func) { if (rc == SQLITE_OK) return; fprintf(stderr, "%s:%d: %s: %s\n", file, line, func, sqlite3_errmsg(db)); sqlite3_close(db); exit(EXIT_FAILURE); } #define sqlite3_check(RC, DB) __sqlite3_check(RC, DB, __FILE__, __LINE__, __func__) static void assert_db_exists(void) { struct stat statbuf __attribute__((unused)); int rc = stat(DATABASE_PATH, &statbuf); if (rc == 0) return; if (errno == ENOENT) { fprintf(stderr, "ftag: database not found at \"%s\", " "have you run \"ftag init\"?\n", DATABASE_PATH); } else { perror(DATABASE_PATH); } exit(EXIT_FAILURE); } static void ftag_init(int, char **) { char cmd[1024]; memset(cmd, 0, sizeof(cmd)); snprintf(cmd, sizeof(cmd)-1, "sqlite3 %s < %s", DATABASE_PATH, FTAG_ROOT "/sql/init.sql"); execl("/usr/bin/sh", "/usr/bin/sh", "-c", cmd, NULL); perror("exec"); exit(EXIT_FAILURE); } static int ftag_print(void *, int, char **cols, char **) { assert(cols[0]); printf("%s\n", cols[0]); return 0; } static void ftag_list_table(const char *table) { char sql[64]; sqlite3 *db = NULL; int rc; rc = sqlite3_open(DATABASE_PATH, &db); sqlite3_check(rc, db); memset(sql, 0, sizeof(sql)); snprintf(sql, sizeof(sql)-1, "SELECT name FROM %s;", table); rc = sqlite3_exec(db, sql, ftag_print, NULL, NULL); sqlite3_check(rc, db); } static void ftag_file_add(int argc, char **argv) { ; } static void ftag_file_list(int argc, char **argv) { ftag_list_table("files"); } static void ftag_file(int argc, char **argv) { assert_db_exists(); const struct ftag_command file_commands[] = { {.name = "add", .func = ftag_file_add}, {.name = "list", .func = ftag_file_list} }; const int file_command_count = sizeof(file_commands) / sizeof(struct ftag_command); parse_args(argc, argv, file_commands, file_command_count); } static void ftag_help(int, char **) { printf("Usage: ftag COMMAND [COMMAND-ARG]...\n"); printf("Available values for COMMAND:\n"); printf(" init initialize the database\n"); printf(" file manage files"); printf(" help print this message\n"); printf(" tag manage tags\n"); printf("Configuration:\n"); printf(" database path %s\n", DATABASE_PATH); printf(" ftag root %s\n", FTAG_ROOT); } /* Check that the tag we are trying to create does not exist yet. If this * callback is ever called, then it already exist, this is a fatal error. */ static int ftag_tag_check(void *, int, char **cols, char **) { assert(cols[0]); assert(cols[1]); fprintf(stderr, "ftag tag add: error: tag \"%s\" already exists, its description is:\n", cols[0]); fprintf(stderr, "%s\n", cols[1]); return 1; } static int ftag_tag_last_id(void *_id, int, char **cols, char **) { int *id = _id; assert(cols); assert(cols[0]); *id = atoi(cols[0]); return 0; } static void ftag_tag_add(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "ftag tag add: error: must supply two arguments, " "the tag name and its description\n"); exit(EXIT_FAILURE); } const char *new_tag_name = argv[0]; assert(strlen(new_tag_name) <= 255); assert(strchr(new_tag_name, '\'') == NULL); char sql[1024]; sqlite3 *db = NULL; int rc; rc = sqlite3_open(DATABASE_PATH, &db); sqlite3_check(rc, db); memset(sql, 0, sizeof(sql)); snprintf(sql, sizeof(sql)-1, "SELECT name, description FROM tags WHERE name = '%s';", new_tag_name); rc = sqlite3_exec(db, sql, ftag_tag_check, NULL, NULL); sqlite3_check(rc, db); int last_id = -1; memset(sql, 0, sizeof(sql)); snprintf(sql, sizeof(sql)-1, "SELECT MAX(id) FROM tags;"); rc = sqlite3_exec(db, sql, ftag_tag_last_id, &last_id, NULL); sqlite3_check(rc, db); const char *new_tag_desc = argv[1]; assert(strlen(new_tag_desc) <= 600); assert(strchr(new_tag_desc, '\'') == NULL); last_id++; memset(sql, 0, sizeof(sql)); snprintf(sql, sizeof(sql)-1, "INSERT INTO tags VALUES(%d, '%s', '%s');", last_id, new_tag_name, new_tag_desc); rc = sqlite3_exec(db, sql, NULL, NULL, NULL); sqlite3_check(rc, db); } static void ftag_tag_list(int argc, char **argv) { ftag_list_table("tags"); } static void ftag_tag(int argc, char **argv) { assert_db_exists(); const struct ftag_command tag_commands[] = { {.name = "add", .func = ftag_tag_add}, {.name = "list", .func = ftag_tag_list} }; int tag_command_count = sizeof(tag_commands) / sizeof(struct ftag_command); parse_args(argc, argv, tag_commands, tag_command_count); } static void parse_args(int argc, char **argv, const struct ftag_command *commands, int command_count) { assert(argc > 0); assert(command_count > 0); for (int i = 0; i < command_count; i++) { if (strcmp(argv[0], commands[i].name) == 0) { commands[i].func(argc-1, argv+1); return; } } fprintf(stderr, "ftag: command \"%s\" unknown\n", argv[0]); exit(EXIT_FAILURE); } int main(int argc, char *argv[]) { if (argc == 1) { ftag_help(0, NULL); exit(EXIT_FAILURE); } const struct ftag_command toplevel_commands[] = { {.name = "init", .func = ftag_init}, {.name = "file", .func = ftag_file}, {.name = "help", .func = ftag_help}, {.name = "tag", .func = ftag_tag} }; const int toplevel_command_count = sizeof(toplevel_commands) / sizeof(struct ftag_command); parse_args(argc-1, argv+1, toplevel_commands, toplevel_command_count); return EXIT_SUCCESS; }