#define _GNU_SOURCE #include <stdio.h> #include <stdint.h> #include <errno.h> #include <assert.h> #include <unistd.h> #include <sys/types.h> #include <pwd.h> #include <assert.h> #include <sysexits.h> #include <ctype.h> #include <mysql.h> #include <string.h> #include "mx_log.h" #include "mx_util.h" #include "mx_mysql.h" #include "mx_getopt.h" #include "mxq_group.h" #include "mxq_job.h" #include "mxq.h" #define UINT64_UNSET (uint64_t)(-1) #define UINT64_ALL (uint64_t)(-2) #define UINT64_SPECIAL_MIN (uint64_t)(-2) #define UINT64_HASVALUE(x) ((x) < UINT64_SPECIAL_MIN) enum mode { MODE_UNSET=0, MODE_CLOSE, MODE_REOPEN }; static void print_usage(void) { mxq_print_generic_version(); printf( "\n" "Usage:\n" " %s [options]\n" "\n" "options:\n" "\n" " -c, --close=GROUPID close group <GROUPID>\n" " -o, --reopen=GROUPID reopen group <GROUPID>\n" "\n" " -v, --verbose be more verbose\n" " --debug set debug log level (default: warning log level)\n" "\n" " -V, --version\n" " -h, --help\n" "\n" "Change how to connect to the mysql server:\n" "\n" " -M, --mysql-default-file[=MYSQLCNF] (default: %s)\n" " -S, --mysql-default-group[=MYSQLGROUP] (default: %s)\n" "\n" "Environment:\n" " MXQ_MYSQL_DEFAULT_FILE change default for MYSQLCNF\n" " MXQ_MYSQL_DEFAULT_GROUP change default for MYSQLGROUP\n" "\n", program_invocation_short_name, MXQ_MYSQL_DEFAULT_FILE_STR, MXQ_MYSQL_DEFAULT_GROUP_STR ); } static int update_group_flags_closed(struct mx_mysql *mysql, uint64_t group_id, uint32_t user_uid) { struct mx_mysql_stmt *stmt = NULL; unsigned long long num_rows = 0; int res; uint64_t newflags = 0; newflags |= MXQ_GROUP_FLAG_CLOSED; stmt = mx_mysql_statement_prepare(mysql, "UPDATE mxq_group SET" " group_flags = group_flags | ?" " WHERE group_id = ?" " AND user_uid = ?" ); if (!stmt) { mx_log_err("mx_mysql_statement_prepare(): %m"); return -(errno=EIO); } res = mx_mysql_statement_param_bind(stmt, 0, uint64, &(newflags)); res += mx_mysql_statement_param_bind(stmt, 1, uint64, &(group_id)); res += mx_mysql_statement_param_bind(stmt, 2, uint32, &(user_uid)); assert(res == 0); res = mx_mysql_statement_execute(stmt, &num_rows); if (res < 0) mx_log_err("mx_mysql_statement_execute(): %m"); mx_mysql_statement_close(&stmt); if (res < 0) return -(errno=-res); assert(num_rows <= 1); return (int)num_rows; } static int update_group_flags_reopen(struct mx_mysql *mysql, uint64_t group_id, uint32_t user_uid) { struct mx_mysql_stmt *stmt = NULL; unsigned long long num_rows = 0; int res; uint64_t newflags = 0; newflags |= MXQ_GROUP_FLAG_CLOSED; stmt = mx_mysql_statement_prepare(mysql, "UPDATE mxq_group SET" " group_flags = group_flags & ~(?)" " WHERE group_id = ?" " AND user_uid = ?" ); if (!stmt) { mx_log_err("mx_mysql_statement_prepare(): %m"); return -(errno=EIO); } res = mx_mysql_statement_param_bind(stmt, 0, uint64, &(newflags)); res += mx_mysql_statement_param_bind(stmt, 1, uint64, &(group_id)); res += mx_mysql_statement_param_bind(stmt, 2, uint32, &(user_uid)); assert(res == 0); res = mx_mysql_statement_execute(stmt, &num_rows); if (res < 0) mx_log_err("mx_mysql_statement_execute(): %m"); mx_mysql_statement_close(&stmt); if (res < 0) return -(errno=-res); assert(num_rows <= 1); return (int)num_rows; } int _close_group_for_user(struct mx_mysql *mysql, uint64_t group_id, uint64_t user_uid) { int res; res = update_group_flags_closed(mysql, group_id, user_uid); if (res == 0) { mx_log_warning("no group with group_id=%lu found for user with uid=%lu", group_id, user_uid); return -(errno=ENOENT); } if (res < 0) { mx_log_err("closing group failed: %m"); return res; } assert(res == 1); mx_log_notice("closing group %lu succeeded.", group_id); return 0; } int _reopen_group_for_user(struct mx_mysql *mysql, uint64_t group_id, uint64_t user_uid) { int res; res = update_group_flags_reopen(mysql, group_id, user_uid); if (res == 0) { mx_log_warning("no group with group_id=%lu found for user with uid=%lu", group_id, user_uid); return -(errno=ENOENT); } if (res < 0) { mx_log_err("opening group failed: %m"); return res; } assert(res == 1); mx_log_notice("opening group %lu succeeded.", group_id); return 0; } int main(int argc, char *argv[]) { struct mx_mysql *mysql = NULL; uid_t ruid, euid, suid; struct passwd *passwd = NULL; int res; uint64_t arg_group_id; char arg_debug; uint64_t arg_uid; enum mode arg_mode; char *arg_mysql_default_group; char *arg_mysql_default_file; int i; int opt; struct mx_getopt_ctl optctl; struct mx_option opts[] = { MX_OPTION_NO_ARG("help", 'h'), MX_OPTION_NO_ARG("version", 'V'), MX_OPTION_NO_ARG("debug", 5), MX_OPTION_NO_ARG("verbose", 'v'), MX_OPTION_REQUIRED_ARG("user", 'u'), MX_OPTION_REQUIRED_ARG("close", 'c'), MX_OPTION_REQUIRED_ARG("reopen", 'o'), MX_OPTION_OPTIONAL_ARG("mysql-default-file", 'M'), MX_OPTION_OPTIONAL_ARG("mysql-default-group", 'S'), MX_OPTION_END }; arg_mysql_default_group = getenv("MXQ_MYSQL_DEFAULT_GROUP"); if (!arg_mysql_default_group) arg_mysql_default_group = MXQ_MYSQL_DEFAULT_GROUP; arg_mysql_default_file = getenv("MXQ_MYSQL_DEFAULT_FILE"); if (!arg_mysql_default_file) arg_mysql_default_file = MXQ_MYSQL_DEFAULT_FILE; arg_group_id = 0; arg_debug = 0; arg_mode = MODE_UNSET; arg_uid = UINT64_UNSET; mx_log_level_set(MX_LOG_NOTICE); res = getresuid(&ruid, &euid, &suid); assert(res != -1); mx_getopt_init(&optctl, argc-1, &argv[1], opts); optctl.flags = MX_FLAG_STOPONNOOPT; while ((opt=mx_getopt(&optctl, &i)) != MX_GETOPT_END) { if (opt == MX_GETOPT_ERROR) { exit(EX_USAGE); } switch (opt) { case 'V': mxq_print_generic_version(); exit(EX_USAGE); case 'h': print_usage(); exit(EX_USAGE); case 5: arg_debug = 1; mx_log_level_set(MX_LOG_DEBUG); break; case 'v': if (!arg_debug) mx_log_level_set(MX_LOG_INFO); break; case 'M': arg_mysql_default_file = optctl.optarg; break; case 'S': arg_mysql_default_group = optctl.optarg; break; case 'u': passwd = getpwnam(optctl.optarg); if (passwd) { arg_uid = passwd->pw_uid; break; } mx_log_debug("user %s not found. trying numeric uid.", optctl.optarg); if (!isdigit(*optctl.optarg)) { mx_log_err("Invalid argument for --user '%s': User not found.", optctl.optarg); exit(EX_USAGE); } if (mx_strtou64(optctl.optarg, &arg_uid) < 0 || arg_uid >= UINT64_SPECIAL_MIN) { if (arg_uid >= UINT64_SPECIAL_MIN) errno = ERANGE; mx_log_err("Invalid argument for --user '%s': %m", optctl.optarg); exit(EX_USAGE); } errno = 0; passwd = getpwuid(arg_uid); if (!passwd) { if (errno) mx_log_err("Can't load user with uid '%lu': %m", arg_uid); else mx_log_err("Invalid argument for --user '%s': User not found.", optctl.optarg); exit(EX_USAGE); } break; case 'c': if (mx_strtou64(optctl.optarg, &arg_group_id) < 0 || !arg_group_id) { if (!arg_group_id) errno = ERANGE; mx_log_err("Invalid argument for --close '%s': %m", optctl.optarg); exit(EX_CONFIG); } arg_mode = MODE_CLOSE; break; case 'o': if (mx_strtou64(optctl.optarg, &arg_group_id) < 0 || !arg_group_id) { if (!arg_group_id) errno = ERANGE; mx_log_err("Invalid argument for --reopen '%s': %m", optctl.optarg); exit(EX_CONFIG); } arg_mode = MODE_REOPEN; break; } } MX_GETOPT_FINISH(optctl, argc, argv); if (!arg_group_id) { print_usage(); exit(EX_USAGE); } if (arg_uid == UINT64_UNSET) arg_uid = ruid; if (arg_uid != ruid && ruid != 0) { mx_log_err("Nice try, but only root user may kill jobs of other users! Better luck next time."); exit(EX_USAGE); } if (!passwd) { errno = 0; passwd = getpwuid(arg_uid); if (!passwd && errno) { mx_log_err("Can't load user with uid '%lu': %m", arg_uid); exit(EX_IOERR); } if (!passwd) { assert(arg_uid == ruid); mx_log_err("Can't load current user with uid '%lu'.", arg_uid); exit(EX_NOUSER); } } res = mx_mysql_initialize(&mysql); assert(res == 0); mx_mysql_option_set_default_file(mysql, arg_mysql_default_file); mx_mysql_option_set_default_group(mysql, arg_mysql_default_group); res = mx_mysql_connect_forever(&mysql); assert(res == 0); mx_log_info("MySQL: Connection to database established."); if (arg_mode == MODE_CLOSE) { res = _close_group_for_user(mysql, arg_group_id, arg_uid); mx_mysql_finish(&mysql); mx_log_info("MySQL: Connection to database closed."); return (res < 0); } else if (arg_mode == MODE_REOPEN) { res = _reopen_group_for_user(mysql, arg_group_id, arg_uid); mx_mysql_finish(&mysql); mx_log_info("MySQL: Connection to database closed."); return (res < 0); } mx_mysql_finish(&mysql); mx_log_info("MySQL: Connection to database closed."); return 1; }