From 567a289ee8be7cf1bdbac78e9330ed6074239865 Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 2 Aug 2022 14:06:42 +0800 Subject: [PATCH 01/91] version 0.0.2 --- Makefile | 6 +- client.c | 1375 +++++++++++++-------------- jobs.c | 2401 ++++++++++++++++++++++++------------------------ list.c | 647 ++++++------- main.c | 1267 +++++++++++++------------ main.h | 372 ++++---- server.c | 857 +++++++++-------- server_start.c | 229 +++-- user.c | 68 ++ user.h | 11 + user.txt | 3 + 11 files changed, 3679 insertions(+), 3557 deletions(-) create mode 100644 user.c create mode 100644 user.h create mode 100644 user.txt diff --git a/Makefile b/Makefile index 852e5ca..7014454 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,8 @@ OBJECTS=main.o \ print.o \ info.o \ env.o \ - tail.o + tail.o \ + user.o TARGET=ts INSTALL=install -c @@ -38,6 +39,7 @@ ifeq ($(GIT_REPO), true) GIT_VERSION=$$(echo $$(git describe --dirty --always --tags) | tr - +); \ $(CC) $(CFLAGS) $(CPPFLAGS) -DTS_VERSION=$${GIT_VERSION} -c $< -o $@ endif +user.o: user.c server_start.o: server_start.c main.h server.o: server.c main.h client.o: client.c main.h @@ -52,7 +54,7 @@ list.o: list.c main.h tail.o: tail.c main.h clean: - rm -f *.o $(TARGET) + rm -f *.o $(TARGET); killall ts; install: $(TARGET) $(INSTALL) -d $(PREFIX)/bin diff --git a/client.c b/client.c index cf07ec7..735f589 100644 --- a/client.c +++ b/client.c @@ -4,14 +4,14 @@ Please find the license in the provided COPYING file. */ +#include #include -#include -#include #include -#include +#include #include #include -#include +#include +#include #include "main.h" @@ -22,860 +22,863 @@ static void c_wait_job_send(); static void c_wait_running_job_send(); char *build_command_string() { - int size; - int i; - int num; - char **array; - char *commandstring; - - size = 0; - num = command_line.command.num; - array = command_line.command.array; - - /* Count bytes needed */ - for (i = 0; i < num; ++i) { - /* The '1' is for spaces, and at the last i, - * for the null character */ - size = size + strlen(array[i]) + 1; - } - - /* Alloc */ - commandstring = (char *) malloc(size); - if (commandstring == NULL) - error("Error in malloc for commandstring"); - - /* Build the command */ - strcpy(commandstring, array[0]); - for (i = 1; i < num; ++i) { - strcat(commandstring, " "); - strcat(commandstring, array[i]); - } - - return commandstring; + int size; + int i; + int num; + char **array; + char *commandstring; + + size = 0; + num = command_line.command.num; + array = command_line.command.array; + + /* Count bytes needed */ + for (i = 0; i < num; ++i) { + /* The '1' is for spaces, and at the last i, + * for the null character */ + size = size + strlen(array[i]) + 1; + } + + /* Alloc */ + commandstring = (char *)malloc(size); + if (commandstring == NULL) + error("Error in malloc for commandstring"); + + /* Build the command */ + strcpy(commandstring, array[0]); + for (i = 1; i < num; ++i) { + strcat(commandstring, " "); + strcat(commandstring, array[i]); + } + + return commandstring; } void c_new_job() { - struct Msg m = default_msg(); - char *new_command; - char *myenv; + struct Msg m = default_msg(); + char *new_command; + char *myenv; - m.type = NEWJOB; + m.type = NEWJOB; - new_command = build_command_string(); + new_command = build_command_string(); - myenv = get_environment(); + myenv = get_environment(); - /* global */ - m.u.newjob.command_size = strlen(new_command) + 1; /* add null */ - if (myenv) - m.u.newjob.env_size = strlen(myenv) + 1; /* add null */ - else - m.u.newjob.env_size = 0; - if (command_line.label) - m.u.newjob.label_size = strlen(command_line.label) + 1; /* add null */ - else - m.u.newjob.label_size = 0; - m.u.newjob.store_output = command_line.store_output; - m.u.newjob.depend_on_size = command_line.depend_on_size; - m.u.newjob.should_keep_finished = command_line.should_keep_finished; - m.u.newjob.command_size = strlen(new_command) + 1; /* add null */ - m.u.newjob.wait_enqueuing = command_line.wait_enqueuing; - m.u.newjob.num_slots = command_line.num_slots; + /* global */ + m.u.newjob.command_size = strlen(new_command) + 1; /* add null */ + m.uid = client_uid; + if (myenv) + m.u.newjob.env_size = strlen(myenv) + 1; /* add null */ + else + m.u.newjob.env_size = 0; + if (command_line.label) + m.u.newjob.label_size = strlen(command_line.label) + 1; /* add null */ + else + m.u.newjob.label_size = 0; + m.u.newjob.store_output = command_line.store_output; + m.u.newjob.depend_on_size = command_line.depend_on_size; + m.u.newjob.should_keep_finished = command_line.should_keep_finished; + m.u.newjob.command_size = strlen(new_command) + 1; /* add null */ + m.u.newjob.wait_enqueuing = command_line.wait_enqueuing; + m.u.newjob.num_slots = command_line.num_slots; - /* Send the message */ - send_msg(server_socket, &m); + /* Send the message */ + send_msg(server_socket, &m); - /* send dependencies */ - if (command_line.depend_on_size) - send_ints(server_socket, command_line.depend_on, command_line.depend_on_size); + /* send dependencies */ + if (command_line.depend_on_size) + send_ints(server_socket, command_line.depend_on, + command_line.depend_on_size); - /* Send the command */ - send_bytes(server_socket, new_command, m.u.newjob.command_size); + /* Send the command */ + send_bytes(server_socket, new_command, m.u.newjob.command_size); - /* Send the label */ - send_bytes(server_socket, command_line.label, m.u.newjob.label_size); + /* Send the label */ + send_bytes(server_socket, command_line.label, m.u.newjob.label_size); - /* Send the environment */ - send_bytes(server_socket, myenv, m.u.newjob.env_size); + /* Send the environment */ + send_bytes(server_socket, myenv, m.u.newjob.env_size); - free(new_command); - free(myenv); + free(new_command); + free(myenv); } int c_wait_newjob_ok() { - struct Msg m = default_msg(); - int res; + struct Msg m = default_msg(); + int res; - res = recv_msg(server_socket, &m); - if (res == -1) - error("Error in wait_newjob_ok"); - if (m.type == NEWJOB_NOK) { - fprintf(stderr, "Error, queue full\n"); - exit(EXITCODE_QUEUE_FULL); - } - if (m.type != NEWJOB_OK) - error("Error getting the newjob_ok"); + res = recv_msg(server_socket, &m); + if (res == -1) + error("Error in wait_newjob_ok"); + if (m.type == NEWJOB_NOK) { + fprintf(stderr, "Error, queue full\n"); + exit(EXITCODE_QUEUE_FULL); + } + if (m.type != NEWJOB_OK) + error("Error getting the newjob_ok"); - return m.u.jobid; + return m.u.jobid; } int c_wait_server_commands() { - struct Msg m = default_msg(); - int res; - - while (1) { - res = recv_msg(server_socket, &m); - if (res == -1) - error("Error in wait_server_commands"); - - if (res == 0) - break; - if (res != sizeof(m)) - error("Error in wait_server_commands"); - if (m.type == RUNJOB) { - struct Result result = default_result(); - result.skipped = 0; - /* These will send RUNJOB_OK */ - if (command_line.depend_on_size && command_line.require_elevel && m.u.last_errorlevel != 0) - { - result.errorlevel = -1; - result.user_ms = 0.; - result.system_ms = 0.; - result.real_ms = 0.; - result.skipped = 1; - c_send_runjob_ok(0, -1); - } - else - run_job(&result); - c_end_of_job(&result); - return result.errorlevel; - } + struct Msg m = default_msg(); + int res; + + while (1) { + res = recv_msg(server_socket, &m); + if (res == -1) + error("Error in wait_server_commands"); + + if (res == 0) + break; + if (res != sizeof(m)) + error("Error in wait_server_commands"); + if (m.type == RUNJOB) { + struct Result result = default_result(); + result.skipped = 0; + /* These will send RUNJOB_OK */ + if (command_line.depend_on_size && command_line.require_elevel && + m.u.last_errorlevel != 0) { + result.errorlevel = -1; + result.user_ms = 0.; + result.system_ms = 0.; + result.real_ms = 0.; + result.skipped = 1; + c_send_runjob_ok(0, -1); + } else + run_job(&result); + c_end_of_job(&result); + return result.errorlevel; } - return -1; + } + return -1; } void c_wait_server_lines() { - struct Msg m = default_msg(); - int res; - - while (1) { - res = recv_msg(server_socket, &m); - if (res == -1) - error("Error in wait_server_lines"); - - if (res == 0) - break; - if (res != sizeof(m)) - error("Error in wait_server_lines 2"); - if (m.type == LIST_LINE) { - char *buffer; - buffer = (char *) malloc(m.u.size); - recv_bytes(server_socket, buffer, m.u.size); - printf("%s", buffer); - free(buffer); - } + struct Msg m = default_msg(); + int res; + + while (1) { + res = recv_msg(server_socket, &m); + if (res == -1) + error("Error in wait_server_lines"); + + if (res == 0) + break; + if (res != sizeof(m)) + error("Error in wait_server_lines 2"); + if (m.type == LIST_LINE) { + char *buffer; + buffer = (char *)malloc(m.u.size); + recv_bytes(server_socket, buffer, m.u.size); + printf("%s", buffer); + free(buffer); } + } } void c_list_jobs() { - struct Msg m = default_msg(); + struct Msg m = default_msg(); - m.type = LIST; - m.u.list.plain_list = command_line.plain_list; - m.u.list.term_width = term_width; - send_msg(server_socket, &m); + m.type = LIST; + m.u.list.plain_list = command_line.plain_list; + m.u.list.term_width = term_width; + send_msg(server_socket, &m); +} + +void c_list_jobs_all() { + struct Msg m = default_msg(); + + m.type = LIST_ALL; + m.u.list.plain_list = command_line.plain_list; + m.u.list.term_width = term_width; + send_msg(server_socket, &m); } /* Exits if wrong */ void c_check_version() { - struct Msg m = default_msg(); - int res; + struct Msg m = default_msg(); + int res; - m.type = GET_VERSION; - /* Double send, so an old ts will answer for sure at least once */ - send_msg(server_socket, &m); - send_msg(server_socket, &m); + m.type = GET_VERSION; + /* Double send, so an old ts will answer for sure at least once */ + send_msg(server_socket, &m); + send_msg(server_socket, &m); - /* Set up a 2 second timeout to receive the - version msg. */ + /* Set up a 2 second timeout to receive the + version msg. */ - res = recv_msg(server_socket, &m); - if (res == -1) - error("Error calling recv_msg in c_check_version"); - if (m.type != VERSION || m.u.version != PROTOCOL_VERSION) { - printf("Wrong server version. Received %i, expecting %i\n", - m.u.version, PROTOCOL_VERSION); + res = recv_msg(server_socket, &m); + if (res == -1) + error("Error calling recv_msg in c_check_version"); + if (m.type != VERSION || m.u.version != PROTOCOL_VERSION) { + printf("Wrong server version. Received %i, expecting %i\n", m.u.version, + PROTOCOL_VERSION); - error("Wrong server version. Received %i, expecting %i", - m.u.version, PROTOCOL_VERSION); - } + error("Wrong server version. Received %i, expecting %i", m.u.version, + PROTOCOL_VERSION); + } - /* Receive also the 2nd send_msg if we got the right version */ - res = recv_msg(server_socket, &m); - if (res == -1) - error("Error calling the 2nd recv_msg in c_check_version"); + /* Receive also the 2nd send_msg if we got the right version */ + res = recv_msg(server_socket, &m); + if (res == -1) + error("Error calling the 2nd recv_msg in c_check_version"); } void c_show_info() { - struct Msg m = default_msg(); - int res; - - m.type = INFO; - m.u.jobid = command_line.jobid; - - send_msg(server_socket, &m); - - while (1) { - res = recv_msg(server_socket, &m); - if (res == -1) - error("Error in wait_server_lines"); - - if (res == 0) - break; - - if (res != sizeof(m)) - error("Error in wait_server_lines 2"); - if (m.type == LIST_LINE) { - char *buffer; - buffer = (char *) malloc(m.u.size); - recv_bytes(server_socket, buffer, m.u.size); - printf("%s", buffer); - free(buffer); - } - if (m.type == INFO_DATA) { - char *buffer; - enum { - DSIZE = 1000 - }; - - /* We're going to output data using the stdout fd */ - fflush(stdout); - buffer = (char *) malloc(DSIZE); - do { - res = recv(server_socket, buffer, DSIZE, 0); - if (res > 0) - write(1, buffer, res); - } while (res > 0); - free(buffer); - } + struct Msg m = default_msg(); + int res; + + m.type = INFO; + m.u.jobid = command_line.jobid; + + send_msg(server_socket, &m); + + while (1) { + res = recv_msg(server_socket, &m); + if (res == -1) + error("Error in wait_server_lines"); + + if (res == 0) + break; + + if (res != sizeof(m)) + error("Error in wait_server_lines 2"); + if (m.type == LIST_LINE) { + char *buffer; + buffer = (char *)malloc(m.u.size); + recv_bytes(server_socket, buffer, m.u.size); + printf("%s", buffer); + free(buffer); } + if (m.type == INFO_DATA) { + char *buffer; + enum { DSIZE = 1000 }; + + /* We're going to output data using the stdout fd */ + fflush(stdout); + buffer = (char *)malloc(DSIZE); + do { + res = recv(server_socket, buffer, DSIZE, 0); + if (res > 0) + write(1, buffer, res); + } while (res > 0); + free(buffer); + } + } } void c_show_last_id() { - struct Msg m = default_msg(); - int res; + struct Msg m = default_msg(); + int res; - m.type = LAST_ID; - send_msg(server_socket, &m); + m.type = LAST_ID; + send_msg(server_socket, &m); - /* Receive the answer */ - res = recv_msg(server_socket, &m); - if (res != sizeof(m)) - error("Error in get_output_file"); + /* Receive the answer */ + res = recv_msg(server_socket, &m); + if (res != sizeof(m)) + error("Error in get_output_file"); - switch (m.type) { - case LAST_ID: - printf("%d\n", m.u.jobid); - default: - warning("Wrong internal message in get_output_file line size"); - } + switch (m.type) { + case LAST_ID: + printf("%d\n", m.u.jobid); + default: + warning("Wrong internal message in get_output_file line size"); + } } void c_send_runjob_ok(const char *ofname, int pid) { - struct Msg m = default_msg(); + struct Msg m = default_msg(); - /* Prepare the message */ - m.type = RUNJOB_OK; - if (ofname) /* ofname == 0, skipped execution */ - m.u.output.store_output = command_line.store_output; - else - m.u.output.store_output = 0; - m.u.output.pid = pid; - if (m.u.output.store_output) - m.u.output.ofilename_size = strlen(ofname) + 1; - else - m.u.output.ofilename_size = 0; + /* Prepare the message */ + m.type = RUNJOB_OK; + if (ofname) /* ofname == 0, skipped execution */ + m.u.output.store_output = command_line.store_output; + else + m.u.output.store_output = 0; + m.u.output.pid = pid; + if (m.u.output.store_output) + m.u.output.ofilename_size = strlen(ofname) + 1; + else + m.u.output.ofilename_size = 0; - send_msg(server_socket, &m); + send_msg(server_socket, &m); - /* Send the filename */ - if (command_line.store_output) - send_bytes(server_socket, ofname, m.u.output.ofilename_size); + /* Send the filename */ + if (command_line.store_output) + send_bytes(server_socket, ofname, m.u.output.ofilename_size); } static void c_end_of_job(const struct Result *res) { - struct Msg m = default_msg(); + struct Msg m = default_msg(); - m.type = ENDJOB; - m.u.result = *res; /* struct copy */ + m.type = ENDJOB; + m.u.result = *res; /* struct copy */ - send_msg(server_socket, &m); + send_msg(server_socket, &m); } void c_shutdown_server() { - struct Msg m = default_msg(); + struct Msg m = default_msg(); - m.type = KILL_SERVER; - send_msg(server_socket, &m); + m.type = KILL_SERVER; + send_msg(server_socket, &m); } void c_clear_finished() { - struct Msg m = default_msg(); + struct Msg m = default_msg(); - m.type = CLEAR_FINISHED; - send_msg(server_socket, &m); + m.type = CLEAR_FINISHED; + send_msg(server_socket, &m); } static char *get_output_file(int *pid) { - struct Msg m = default_msg(); - int res; - char *string = 0; - - /* Send the request */ - m.type = ASK_OUTPUT; - m.u.jobid = command_line.jobid; - send_msg(server_socket, &m); - - /* Receive the answer */ - res = recv_msg(server_socket, &m); - if (res != sizeof(m)) - error("Error in get_output_file"); - switch (m.type) { - case ANSWER_OUTPUT: - if (m.u.output.store_output) { - /* Receive the output file name */ - string = 0; - if (m.u.output.ofilename_size > 0) { - string = (char *) malloc(m.u.output.ofilename_size); - recv_bytes(server_socket, string, m.u.output.ofilename_size); - } - *pid = m.u.output.pid; - return string; - } - *pid = m.u.output.pid; - return 0; - /* WILL NOT GO FURTHER */ - case LIST_LINE: /* Only ONE line accepted */ - string = (char *) malloc(m.u.size); - res = recv_bytes(server_socket, string, m.u.size); - if (res != m.u.size) - error("Error in get_output_file line size"); - fprintf(stderr, "Error in the request: %s", - string); - free(string); - exit(-1); - /* WILL NOT GO FURTHER */ - default: - warning("Wrong internal message in get_output_file line size"); + struct Msg m = default_msg(); + int res; + char *string = 0; + + /* Send the request */ + m.type = ASK_OUTPUT; + m.u.jobid = command_line.jobid; + send_msg(server_socket, &m); + + /* Receive the answer */ + res = recv_msg(server_socket, &m); + if (res != sizeof(m)) + error("Error in get_output_file"); + switch (m.type) { + case ANSWER_OUTPUT: + if (m.u.output.store_output) { + /* Receive the output file name */ + string = 0; + if (m.u.output.ofilename_size > 0) { + string = (char *)malloc(m.u.output.ofilename_size); + recv_bytes(server_socket, string, m.u.output.ofilename_size); + } + *pid = m.u.output.pid; + return string; } - /* This will never be reached */ + *pid = m.u.output.pid; return 0; + /* WILL NOT GO FURTHER */ + case LIST_LINE: /* Only ONE line accepted */ + string = (char *)malloc(m.u.size); + res = recv_bytes(server_socket, string, m.u.size); + if (res != m.u.size) + error("Error in get_output_file line size"); + fprintf(stderr, "Error in the request: %s", string); + free(string); + exit(-1); + /* WILL NOT GO FURTHER */ + default: + warning("Wrong internal message in get_output_file line size"); + } + /* This will never be reached */ + return 0; } int c_tail() { - char *str; - int pid; - str = get_output_file(&pid); - if (str == 0) { - fprintf(stderr, "The output is not stored. Cannot tail.\n"); - exit(-1); - } + char *str; + int pid; + str = get_output_file(&pid); + if (str == 0) { + fprintf(stderr, "The output is not stored. Cannot tail.\n"); + exit(-1); + } - c_wait_running_job_send(); + c_wait_running_job_send(); - return tail_file(str, 10 /* Last lines to show */); + return tail_file(str, 10 /* Last lines to show */); } int c_cat() { - char *str; - int pid; - str = get_output_file(&pid); - if (str == 0) { - fprintf(stderr, "The output is not stored. Cannot cat.\n"); - exit(-1); - } - c_wait_running_job_send(); + char *str; + int pid; + str = get_output_file(&pid); + if (str == 0) { + fprintf(stderr, "The output is not stored. Cannot cat.\n"); + exit(-1); + } + c_wait_running_job_send(); - return tail_file(str, -1 /* All the lines */); + return tail_file(str, -1 /* All the lines */); } void c_show_output_file() { - char *str; - int pid; - /* This will exit if there is any error */ - str = get_output_file(&pid); - if (str == 0) { - fprintf(stderr, "The output is not stored.\n"); - exit(-1); - } - printf("%s\n", str); - free(str); + char *str; + int pid; + /* This will exit if there is any error */ + str = get_output_file(&pid); + if (str == 0) { + fprintf(stderr, "The output is not stored.\n"); + exit(-1); + } + printf("%s\n", str); + free(str); } void c_show_pid() { - int pid; - /* This will exit if there is any error */ - get_output_file(&pid); - printf("%i\n", pid); + int pid; + /* This will exit if there is any error */ + get_output_file(&pid); + printf("%i\n", pid); } void c_kill_job() { - int pid = 0; - /* This will exit if there is any error */ - get_output_file(&pid); + int pid = 0; + /* This will exit if there is any error */ + get_output_file(&pid); - if (pid == -1 || pid == 0) { - fprintf(stderr, "Error: strange PID received: %i\n", pid); - exit(-1); - } + if (pid == -1 || pid == 0) { + fprintf(stderr, "Error: strange PID received: %i\n", pid); + exit(-1); + } - /* Send SIGTERM to the process group, as pid is for process group */ - kill(-pid, SIGTERM); + /* Send SIGTERM to the process group, as pid is for process group */ + kill(-pid, SIGTERM); } void c_kill_all_jobs() { - struct Msg m = default_msg(); - int res; - - /* Send the request */ - m.type = KILL_ALL; - send_msg(server_socket, &m); - - /* Receive the answer */ - res = recv_msg(server_socket, &m); - if (res != sizeof(m)) - error("Error in kill_all"); - switch (m.type) { - case COUNT_RUNNING: - for (int i = 0; i < m.u.count_running; ++i) { - int pid; - res = recv(server_socket, &pid, sizeof(int), 0); - if (res != sizeof(int)) - error("Error in receiving PID kill_all"); - kill(-pid, SIGTERM); - } - return; - default: - warning("Wrong internal message in kill_all"); + struct Msg m = default_msg(); + int res; + + /* Send the request */ + m.type = KILL_ALL; + send_msg(server_socket, &m); + + /* Receive the answer */ + res = recv_msg(server_socket, &m); + if (res != sizeof(m)) + error("Error in kill_all"); + switch (m.type) { + case COUNT_RUNNING: + for (int i = 0; i < m.u.count_running; ++i) { + int pid; + res = recv(server_socket, &pid, sizeof(int), 0); + if (res != sizeof(int)) + error("Error in receiving PID kill_all"); + kill(-pid, SIGTERM); } + return; + default: + warning("Wrong internal message in kill_all"); + } } void c_remove_job() { - struct Msg m = default_msg(); - int res; - char *string = 0; - - /* Send the request */ - m.type = REMOVEJOB; - m.u.jobid = command_line.jobid; - send_msg(server_socket, &m); - - /* Receive the answer */ - res = recv_msg(server_socket, &m); - if (res != sizeof(m)) - error("Error in remove_job"); - switch (m.type) { - case REMOVEJOB_OK: - return; - /* WILL NOT GO FURTHER */ - case LIST_LINE: /* Only ONE line accepted */ - string = (char *) malloc(m.u.size); - res = recv_bytes(server_socket, string, m.u.size); - fprintf(stderr, "Error in the request: %s", - string); - free(string); - exit(-1); - /* WILL NOT GO FURTHER */ - default: - warning("Wrong internal message in remove_job"); - } - /* This will never be reached */ + struct Msg m = default_msg(); + int res; + char *string = 0; + + /* Send the request */ + m.type = REMOVEJOB; + m.u.jobid = command_line.jobid; + m.uid = client_uid; + send_msg(server_socket, &m); + + /* Receive the answer */ + res = recv_msg(server_socket, &m); + if (res != sizeof(m)) + error("Error in remove_job"); + switch (m.type) { + case REMOVEJOB_OK: + return; + /* WILL NOT GO FURTHER */ + case LIST_LINE: /* Only ONE line accepted */ + string = (char *)malloc(m.u.size); + res = recv_bytes(server_socket, string, m.u.size); + fprintf(stderr, "Error in the request: %s", string); + free(string); + exit(-1); + /* WILL NOT GO FURTHER */ + default: + warning("Wrong internal message in remove_job"); + } + /* This will never be reached */ } int c_wait_job_recv() { - struct Msg m = default_msg(); - int res; - char *string = 0; - - /* Receive the answer */ - res = recv_msg(server_socket, &m); - if (res != sizeof(m)) - error("Error in wait_job"); - switch (m.type) { - case WAITJOB_OK: - return m.u.result.errorlevel; - /* WILL NOT GO FURTHER */ - case LIST_LINE: /* Only ONE line accepted */ - string = (char *) malloc(m.u.size); - res = recv_bytes(server_socket, string, m.u.size); - if (res != m.u.size) - error("Error in wait_job - line size"); - fprintf(stderr, "Error in the request: %s", - string); - free(string); - exit(-1); - /* WILL NOT GO FURTHER */ - default: - warning("Wrong internal message in c_wait_job"); - } - /* This will never be reached */ - return -1; + struct Msg m = default_msg(); + int res; + char *string = 0; + + /* Receive the answer */ + res = recv_msg(server_socket, &m); + if (res != sizeof(m)) + error("Error in wait_job"); + switch (m.type) { + case WAITJOB_OK: + return m.u.result.errorlevel; + /* WILL NOT GO FURTHER */ + case LIST_LINE: /* Only ONE line accepted */ + string = (char *)malloc(m.u.size); + res = recv_bytes(server_socket, string, m.u.size); + if (res != m.u.size) + error("Error in wait_job - line size"); + fprintf(stderr, "Error in the request: %s", string); + free(string); + exit(-1); + /* WILL NOT GO FURTHER */ + default: + warning("Wrong internal message in c_wait_job"); + } + /* This will never be reached */ + return -1; } static void c_wait_job_send() { - struct Msg m = default_msg(); + struct Msg m = default_msg(); - /* Send the request */ - m.type = WAITJOB; - m.u.jobid = command_line.jobid; - send_msg(server_socket, &m); + /* Send the request */ + m.type = WAITJOB; + m.u.jobid = command_line.jobid; + send_msg(server_socket, &m); } static void c_wait_running_job_send() { - struct Msg m = default_msg(); + struct Msg m = default_msg(); - /* Send the request */ - m.type = WAIT_RUNNING_JOB; - m.u.jobid = command_line.jobid; - send_msg(server_socket, &m); + /* Send the request */ + m.type = WAIT_RUNNING_JOB; + m.u.jobid = command_line.jobid; + send_msg(server_socket, &m); } /* Returns the errorlevel */ int c_wait_job() { - c_wait_job_send(); - return c_wait_job_recv(); + c_wait_job_send(); + return c_wait_job_recv(); } /* Returns the errorlevel */ int c_wait_running_job() { - c_wait_running_job_send(); - return c_wait_job_recv(); + c_wait_running_job_send(); + return c_wait_job_recv(); } void c_send_max_slots(int max_slots) { - struct Msg m = default_msg(); + struct Msg m = default_msg(); - /* Send the request */ - m.type = SET_MAX_SLOTS; - m.u.max_slots = command_line.max_slots; - send_msg(server_socket, &m); + /* Send the request */ + m.type = SET_MAX_SLOTS; + m.u.max_slots = command_line.max_slots; + send_msg(server_socket, &m); } void c_get_max_slots() { - struct Msg m = default_msg(); - int res; - - /* Send the request */ - m.type = GET_MAX_SLOTS; - m.u.max_slots = command_line.max_slots; - send_msg(server_socket, &m); - - /* Receive the answer */ - res = recv_msg(server_socket, &m); - if (res != sizeof(m)) - error("Error in move_urgent"); - switch (m.type) { - case GET_MAX_SLOTS_OK: - printf("%i\n", m.u.max_slots); - return; - default: - warning("Wrong internal message in get_max_slots"); - } + struct Msg m = default_msg(); + int res; + + /* Send the request */ + m.type = GET_MAX_SLOTS; + m.u.max_slots = command_line.max_slots; + send_msg(server_socket, &m); + + /* Receive the answer */ + res = recv_msg(server_socket, &m); + if (res != sizeof(m)) + error("Error in move_urgent"); + switch (m.type) { + case GET_MAX_SLOTS_OK: + printf("%i\n", m.u.max_slots); + return; + default: + warning("Wrong internal message in get_max_slots"); + } } void c_move_urgent() { - struct Msg m = default_msg(); - int res; - char *string = 0; - - /* Send the request */ - m.type = URGENT; - m.u.jobid = command_line.jobid; - send_msg(server_socket, &m); - - /* Receive the answer */ - res = recv_msg(server_socket, &m); - if (res != sizeof(m)) - error("Error in move_urgent"); - switch (m.type) { - case URGENT_OK: - return; - /* WILL NOT GO FURTHER */ - case LIST_LINE: /* Only ONE line accepted */ - string = (char *) malloc(m.u.size); - res = recv_bytes(server_socket, string, m.u.size); - if (res != m.u.size) - error("Error in move_urgent - line size"); - fprintf(stderr, "Error in the request: %s", - string); - free(string); - exit(-1); - /* WILL NOT GO FURTHER */ - default: - warning("Wrong internal message in move_urgent"); - } - /* This will never be reached */ + struct Msg m = default_msg(); + int res; + char *string = 0; + + /* Send the request */ + m.type = URGENT; + m.u.jobid = command_line.jobid; + send_msg(server_socket, &m); + + /* Receive the answer */ + res = recv_msg(server_socket, &m); + if (res != sizeof(m)) + error("Error in move_urgent"); + switch (m.type) { + case URGENT_OK: return; + /* WILL NOT GO FURTHER */ + case LIST_LINE: /* Only ONE line accepted */ + string = (char *)malloc(m.u.size); + res = recv_bytes(server_socket, string, m.u.size); + if (res != m.u.size) + error("Error in move_urgent - line size"); + fprintf(stderr, "Error in the request: %s", string); + free(string); + exit(-1); + /* WILL NOT GO FURTHER */ + default: + warning("Wrong internal message in move_urgent"); + } + /* This will never be reached */ + return; } void c_get_state() { - struct Msg m = default_msg(); - int res; - char *string = 0; - - /* Send the request */ - m.type = GET_STATE; - m.u.jobid = command_line.jobid; - send_msg(server_socket, &m); - - /* Receive the answer */ - res = recv_msg(server_socket, &m); - if (res != sizeof(m)) - error("Error in get_state - line size"); - switch (m.type) { - case ANSWER_STATE: - printf("%s\n", jstate2string(m.u.state)); - return; - /* WILL NOT GO FURTHER */ - case LIST_LINE: /* Only ONE line accepted */ - string = (char *) malloc(m.u.size); - res = recv_bytes(server_socket, string, m.u.size); - if (res != m.u.size) - error("Error in get_state - line size"); - fprintf(stderr, "Error in the request: %s", - string); - free(string); - exit(-1); - /* WILL NOT GO FURTHER */ - default: - warning("Wrong internal message in get_state"); - } - /* This will never be reached */ + struct Msg m = default_msg(); + int res; + char *string = 0; + + /* Send the request */ + m.type = GET_STATE; + m.u.jobid = command_line.jobid; + send_msg(server_socket, &m); + + /* Receive the answer */ + res = recv_msg(server_socket, &m); + if (res != sizeof(m)) + error("Error in get_state - line size"); + switch (m.type) { + case ANSWER_STATE: + printf("%s\n", jstate2string(m.u.state)); return; + /* WILL NOT GO FURTHER */ + case LIST_LINE: /* Only ONE line accepted */ + string = (char *)malloc(m.u.size); + res = recv_bytes(server_socket, string, m.u.size); + if (res != m.u.size) + error("Error in get_state - line size"); + fprintf(stderr, "Error in the request: %s", string); + free(string); + exit(-1); + /* WILL NOT GO FURTHER */ + default: + warning("Wrong internal message in get_state"); + } + /* This will never be reached */ + return; } void c_swap_jobs() { - struct Msg m = default_msg(); - int res; - char *string = 0; - - /* Send the request */ - m.type = SWAP_JOBS; - m.u.swap.jobid1 = command_line.jobid; - m.u.swap.jobid2 = command_line.jobid2; - send_msg(server_socket, &m); - - /* Receive the answer */ - res = recv_msg(server_socket, &m); - if (res != sizeof(m)) - error("Error in swap_jobs"); - switch (m.type) { - case SWAP_JOBS_OK: - return; - /* WILL NOT GO FURTHER */ - case LIST_LINE: /* Only ONE line accepted */ - string = (char *) malloc(m.u.size); - res = recv_bytes(server_socket, string, m.u.size); - if (res != m.u.size) - error("Error in swap_jobs - line size"); - fprintf(stderr, "Error in the request: %s", - string); - free(string); - exit(-1); - /* WILL NOT GO FURTHER */ - default: - warning("Wrong internal message in swap_jobs"); - } - /* This will never be reached */ + struct Msg m = default_msg(); + int res; + char *string = 0; + + /* Send the request */ + m.type = SWAP_JOBS; + m.u.swap.jobid1 = command_line.jobid; + m.u.swap.jobid2 = command_line.jobid2; + send_msg(server_socket, &m); + + /* Receive the answer */ + res = recv_msg(server_socket, &m); + if (res != sizeof(m)) + error("Error in swap_jobs"); + switch (m.type) { + case SWAP_JOBS_OK: return; + /* WILL NOT GO FURTHER */ + case LIST_LINE: /* Only ONE line accepted */ + string = (char *)malloc(m.u.size); + res = recv_bytes(server_socket, string, m.u.size); + if (res != m.u.size) + error("Error in swap_jobs - line size"); + fprintf(stderr, "Error in the request: %s", string); + free(string); + exit(-1); + /* WILL NOT GO FURTHER */ + default: + warning("Wrong internal message in swap_jobs"); + } + /* This will never be reached */ + return; } void c_get_count_running() { - struct Msg m = default_msg(); - int res; + struct Msg m = default_msg(); + int res; - /* Send the request */ - m.type = COUNT_RUNNING; - send_msg(server_socket, &m); + /* Send the request */ + m.type = COUNT_RUNNING; + send_msg(server_socket, &m); - /* Receive the answer */ - res = recv_msg(server_socket, &m); - if (res != sizeof(m)) - error("Error in count_running - line size"); - - switch (m.type) { - case COUNT_RUNNING: - printf("%i\n", m.u.count_running); - return; - default: - warning("Wrong internal message in count_running"); - } + /* Receive the answer */ + res = recv_msg(server_socket, &m); + if (res != sizeof(m)) + error("Error in count_running - line size"); - /* This will never be reached */ + switch (m.type) { + case COUNT_RUNNING: + printf("%i\n", m.u.count_running); return; + default: + warning("Wrong internal message in count_running"); + } + + /* This will never be reached */ + return; } void c_show_label() { - struct Msg m = default_msg(); - int res; - char *string = 0; - - /* Send the request */ - m.type = GET_LABEL; - m.u.jobid = command_line.jobid; - send_msg(server_socket, &m); - - /* Receive the answer */ - res = recv_msg(server_socket, &m); - if (res != sizeof(m)) - error("Error in get_label"); - - switch (m.type) { - case LIST_LINE: - string = (char *) malloc(m.u.size); - res = recv_bytes(server_socket, string, m.u.size); - if (res != m.u.size) - error("Error in get_label - line size"); - - printf("%s", string); - free(string); - return; - default: - warning("Wrong internal message in get_label"); - } - - /* This will never be reached */ + struct Msg m = default_msg(); + int res; + char *string = 0; + + /* Send the request */ + m.type = GET_LABEL; + m.u.jobid = command_line.jobid; + send_msg(server_socket, &m); + + /* Receive the answer */ + res = recv_msg(server_socket, &m); + if (res != sizeof(m)) + error("Error in get_label"); + + switch (m.type) { + case LIST_LINE: + string = (char *)malloc(m.u.size); + res = recv_bytes(server_socket, string, m.u.size); + if (res != m.u.size) + error("Error in get_label - line size"); + + printf("%s", string); + free(string); return; + default: + warning("Wrong internal message in get_label"); + } + + /* This will never be reached */ + return; } void c_show_cmd() { - struct Msg m = default_msg(); - int res; - char *string = 0; - - /* Send the request */ - m.type = GET_CMD; - m.u.jobid = command_line.jobid; - send_msg(server_socket, &m); - - /* Receive the answer */ - res = recv_msg(server_socket, &m); - if (res != sizeof(m)) - error("Error in show_cmd"); - - switch (m.type) { - case LIST_LINE: - string = (char *) malloc(m.u.size); - res = recv_bytes(server_socket, string, m.u.size); - if (res != m.u.size) - error("Error in show_cmd - line size"); - - printf("%s", string); - free(string); - return; - default: - warning("Wrong internal message in show_cmd"); + struct Msg m = default_msg(); + int res; + char *string = 0; + + /* Send the request */ + m.type = GET_CMD; + m.u.jobid = command_line.jobid; + send_msg(server_socket, &m); + + /* Receive the answer */ + res = recv_msg(server_socket, &m); + if (res != sizeof(m)) + error("Error in show_cmd"); + + switch (m.type) { + case LIST_LINE: + string = (char *)malloc(m.u.size); + res = recv_bytes(server_socket, string, m.u.size); + if (res != m.u.size) + error("Error in show_cmd - line size"); + + printf("%s", string); + free(string); + return; + default: + warning("Wrong internal message in show_cmd"); + } +} + +char *get_logdir() { + struct Msg m = default_msg(); + int res; + char *string = 0; + + /* Send the request */ + m.type = GET_LOGDIR; + send_msg(server_socket, &m); + + /* Receive the answer */ + res = recv_msg(server_socket, &m); + if (res != sizeof(m)) + error("Error in get_logdir"); + + switch (m.type) { + case LIST_LINE: + string = (char *)malloc(m.u.size); + res = recv_bytes(server_socket, string, m.u.size); + if (res != m.u.size) { + free(string); + error("Error in get_logdir - line size"); } -} -char* get_logdir() { - struct Msg m = default_msg(); - int res; - char *string = 0; - - /* Send the request */ - m.type = GET_LOGDIR; - send_msg(server_socket, &m); - - /* Receive the answer */ - res = recv_msg(server_socket, &m); - if (res != sizeof(m)) - error("Error in get_logdir"); - - switch (m.type) { - case LIST_LINE: - string = (char *) malloc(m.u.size); - res = recv_bytes(server_socket, string, m.u.size); - if (res != m.u.size) { - free(string); - error("Error in get_logdir - line size"); - } - - return string; - default: - warning("Wrong internal message in get_logdir"); - } return string; + default: + warning("Wrong internal message in get_logdir"); + } + return string; } void c_get_logdir() { - char* path; - path = get_logdir(); - printf("%s\n", path); - free(path); + char *path; + path = get_logdir(); + printf("%s\n", path); + free(path); } void c_set_logdir() { - struct Msg m = default_msg(); + struct Msg m = default_msg(); - /* Send the request */ - m.type = SET_LOGDIR; - m.u.size = strlen(command_line.label) + 1; - send_msg(server_socket, &m); - send_bytes(server_socket, command_line.label, m.u.size); + /* Send the request */ + m.type = SET_LOGDIR; + m.u.size = strlen(command_line.label) + 1; + send_msg(server_socket, &m); + send_bytes(server_socket, command_line.label, m.u.size); } void c_get_env() { - struct Msg m = default_msg(); - int res; - char *string = 0; + struct Msg m = default_msg(); + int res; + char *string = 0; + + /* Send the request */ + m.type = GET_ENV; + m.u.size = strlen(command_line.label) + 1; + send_msg(server_socket, &m); + send_bytes(server_socket, command_line.label, m.u.size); + + /* Receive the answer */ + res = recv_msg(server_socket, &m); + if (res != sizeof(m)) + error("Error in get_env"); + + switch (m.type) { + case LIST_LINE: + if (m.u.size) { + string = (char *)malloc(m.u.size); + res = recv_bytes(server_socket, string, m.u.size); + if (res != m.u.size) + error("Error in get_env - line size"); + + printf("%s\n", string); + free(string); + } else + printf("\n"); - /* Send the request */ - m.type = GET_ENV; - m.u.size = strlen(command_line.label) + 1; - send_msg(server_socket, &m); - send_bytes(server_socket, command_line.label, m.u.size); - - /* Receive the answer */ - res = recv_msg(server_socket, &m); - if (res != sizeof(m)) - error("Error in get_env"); - - switch (m.type) { - case LIST_LINE: - if (m.u.size) { - string = (char *) malloc(m.u.size); - res = recv_bytes(server_socket, string, m.u.size); - if (res != m.u.size) - error("Error in get_env - line size"); - - printf("%s\n", string); - free(string); - } else - printf("\n"); - - return; - default: - warning("Wrong internal message in get_env"); - } + return; + default: + warning("Wrong internal message in get_env"); + } } void c_set_env() { - struct Msg m = default_msg(); + struct Msg m = default_msg(); - /* Send the request */ - m.type = SET_ENV; - m.u.size = strlen(command_line.label) + 1; - send_msg(server_socket, &m); - send_bytes(server_socket, command_line.label, m.u.size); + /* Send the request */ + m.type = SET_ENV; + m.u.size = strlen(command_line.label) + 1; + send_msg(server_socket, &m); + send_bytes(server_socket, command_line.label, m.u.size); } void c_unset_env() { - struct Msg m = default_msg(); + struct Msg m = default_msg(); - /* Send the request */ - m.type = UNSET_ENV; - m.u.size = strlen(command_line.label) + 1; - send_msg(server_socket, &m); - send_bytes(server_socket, command_line.label, m.u.size); + /* Send the request */ + m.type = UNSET_ENV; + m.u.size = strlen(command_line.label) + 1; + send_msg(server_socket, &m); + send_bytes(server_socket, command_line.label, m.u.size); } diff --git a/jobs.c b/jobs.c index 4058bc4..0c4e30e 100644 --- a/jobs.c +++ b/jobs.c @@ -5,25 +5,27 @@ Please find the license in the provided COPYING file. */ #define _DEFAULT_SOURCE -#include -#include +#include +#include #include +#include #include +#include #include #include -#include -#include +#include #include "main.h" +#include "user.h" /* The list will access them */ int busy_slots = 0; int max_slots = 1; struct Notify { - int socket; - int jobid; - struct Notify *next; + int socket; + int jobid; + struct Notify *next; }; /* Globals */ @@ -46,1532 +48,1569 @@ static struct Job *get_job(int jobid); void notify_errorlevel(struct Job *p); -static void destroy_job(struct Job* p) { - free(p->notify_errorlevel_to); - free(p->command); - free(p->output_filename); - pinfo_free(&p->info); - free(p->depend_on); - free(p->label); - free(p); +static void destroy_job(struct Job *p) { + free(p->notify_errorlevel_to); + free(p->command); + free(p->output_filename); + pinfo_free(&p->info); + free(p->depend_on); + free(p->label); + free(p); } -static void send_list_line(int s, const char *str) { - struct Msg m = default_msg(); +void send_list_line(int s, const char *str) { + struct Msg m = default_msg(); - /* Message */ - m.type = LIST_LINE; - m.u.size = strlen(str) + 1; + /* Message */ + m.type = LIST_LINE; + m.u.size = strlen(str) + 1; - send_msg(s, &m); + send_msg(s, &m); - /* Send the line */ - send_bytes(s, str, m.u.size); + /* Send the line */ + send_bytes(s, str, m.u.size); } static void send_urgent_ok(int s) { - struct Msg m = default_msg(); + struct Msg m = default_msg(); - /* Message */ - m.type = URGENT_OK; + /* Message */ + m.type = URGENT_OK; - send_msg(s, &m); + send_msg(s, &m); } static void send_swap_jobs_ok(int s) { - struct Msg m = default_msg(); + struct Msg m = default_msg(); - /* Message */ - m.type = SWAP_JOBS_OK; + /* Message */ + m.type = SWAP_JOBS_OK; - send_msg(s, &m); + send_msg(s, &m); } static struct Job *find_previous_job(const struct Job *final) { - struct Job *p; + struct Job *p; - /* Show Queued or Running jobs */ - p = firstjob; - while (p != 0) { - if (p->next == final) - return p; - p = p->next; - } + /* Show Queued or Running jobs */ + p = firstjob; + while (p != 0) { + if (p->next == final) + return p; + p = p->next; + } - return 0; + return 0; } static struct Job *findjob(int jobid) { - struct Job *p; + struct Job *p; - /* Show Queued or Running jobs */ - p = firstjob; - while (p != 0) { - if (p->jobid == jobid) - return p; - p = p->next; - } + /* Show Queued or Running jobs */ + p = firstjob; + while (p != 0) { + if (p->jobid == jobid) + return p; + p = p->next; + } - return 0; + return 0; } static struct Job *findjob_holding_client() { - struct Job *p; + struct Job *p; - /* Show Queued or Running jobs */ - p = firstjob; - while (p != 0) { - if (p->state == HOLDING_CLIENT) - return p; - p = p->next; - } + /* Show Queued or Running jobs */ + p = firstjob; + while (p != 0) { + if (p->state == HOLDING_CLIENT) + return p; + p = p->next; + } - return 0; + return 0; } static struct Job *find_finished_job(int jobid) { - struct Job *p; + struct Job *p; - /* Show Queued or Running jobs */ - p = first_finished_job; - while (p != 0) { - if (p->jobid == jobid) - return p; - p = p->next; - } + /* Show Queued or Running jobs */ + p = first_finished_job; + while (p != 0) { + if (p->jobid == jobid) + return p; + p = p->next; + } - return 0; + return 0; } static int count_not_finished_jobs() { - int count = 0; - struct Job *p; + int count = 0; + struct Job *p; - /* Show Queued or Running jobs */ - p = firstjob; - while (p != 0) { - ++count; - p = p->next; - } - return count; + /* Show Queued or Running jobs */ + p = firstjob; + while (p != 0) { + ++count; + p = p->next; + } + return count; } static void add_notify_errorlevel_to(struct Job *job, int jobid) { - int *p; - int newsize = (job->notify_errorlevel_to_size + 1) - * sizeof(int); - p = (int *) realloc(job->notify_errorlevel_to, - newsize); - - if (p == 0) - error("Cannot allocate more memory for notify_errorlist_to for jobid %i," - " having already %i elements", - job->jobid, job->notify_errorlevel_to_size); - - job->notify_errorlevel_to = p; - job->notify_errorlevel_to_size += 1; - job->notify_errorlevel_to[job->notify_errorlevel_to_size - 1] = jobid; + int *p; + int newsize = (job->notify_errorlevel_to_size + 1) * sizeof(int); + p = (int *)realloc(job->notify_errorlevel_to, newsize); + + if (p == 0) + error("Cannot allocate more memory for notify_errorlist_to for jobid %i," + " having already %i elements", + job->jobid, job->notify_errorlevel_to_size); + + job->notify_errorlevel_to = p; + job->notify_errorlevel_to_size += 1; + job->notify_errorlevel_to[job->notify_errorlevel_to_size - 1] = jobid; } void s_kill_all_jobs(int s) { - struct Job *p; - s_count_running_jobs(s); + struct Job *p; + s_count_running_jobs(s); - /* send running job PIDs */ - p = firstjob; - while (p != 0) { - if (p->state == RUNNING) - send(s, &p->pid, sizeof(int), 0); + /* send running job PIDs */ + p = firstjob; + while (p != 0) { + if (p->state == RUNNING) + send(s, &p->pid, sizeof(int), 0); - p = p->next; - } + p = p->next; + } } void s_count_running_jobs(int s) { - int count = 0; - struct Job *p; - struct Msg m = default_msg(); + int count = 0; + struct Job *p; + struct Msg m = default_msg(); - /* Count running jobs */ - p = firstjob; - while (p != 0) { - if (p->state == RUNNING) - ++count; + /* Count running jobs */ + p = firstjob; + while (p != 0) { + if (p->state == RUNNING) + ++count; - p = p->next; - } + p = p->next; + } - /* Message */ - m.type = COUNT_RUNNING; - m.u.count_running = count; - send_msg(s, &m); + /* Message */ + m.type = COUNT_RUNNING; + m.u.count_running = count; + send_msg(s, &m); } void s_get_label(int s, int jobid) { - struct Job *p = 0; - char *label; - - if (jobid == -1) { - /* Find the last job added */ - p = firstjob; - - if (p != 0) - while (p->next != 0) - p = p->next; - - /* Look in finished jobs if needed */ - if (p == 0) { - p = first_finished_job; - if (p != 0) - while (p->next != 0) - p = p->next; - } + struct Job *p = 0; + char *label; - } else { - p = get_job(jobid); - } + if (jobid == -1) { + /* Find the last job added */ + p = firstjob; + if (p != 0) + while (p->next != 0) + p = p->next; + + /* Look in finished jobs if needed */ if (p == 0) { - char tmp[50]; - sprintf(tmp, "Job %i not finished or not running.\n", jobid); - send_list_line(s, tmp); - return; + p = first_finished_job; + if (p != 0) + while (p->next != 0) + p = p->next; } - if (p->label) { - label = (char *) malloc(strlen(p->label) + 1); - sprintf(label, "%s\n", p->label); - } else - label = ""; - send_list_line(s, label); - if (p->label) - free(label); + } else { + p = get_job(jobid); + } + + if (p == 0) { + char tmp[50]; + sprintf(tmp, "Job %i not finished or not running.\n", jobid); + send_list_line(s, tmp); + return; + } + + if (p->label) { + label = (char *)malloc(strlen(p->label) + 1); + sprintf(label, "%s\n", p->label); + } else + label = ""; + send_list_line(s, label); + if (p->label) + free(label); } void s_send_cmd(int s, int jobid) { - struct Job *p = 0; - char *cmd; - - if (jobid == -1) { - /* Find the last job added */ - p = firstjob; - - if (p != 0) - while (p->next != 0) - p = p->next; - - /* Look in finished jobs if needed */ - if (p == 0) { - p = first_finished_job; - if (p != 0) - while (p->next != 0) - p = p->next; - } + struct Job *p = 0; + char *cmd; - } else { - p = get_job(jobid); - } + if (jobid == -1) { + /* Find the last job added */ + p = firstjob; + if (p != 0) + while (p->next != 0) + p = p->next; + + /* Look in finished jobs if needed */ if (p == 0) { - char tmp[50]; - sprintf(tmp, "Job %i not finished or not running.\n", jobid); - send_list_line(s, tmp); - return; + p = first_finished_job; + if (p != 0) + while (p->next != 0) + p = p->next; } - cmd = (char *) malloc(strlen(p->command) + 1); - sprintf(cmd, "%s\n", p->command); - send_list_line(s, cmd); - free(cmd); + + } else { + p = get_job(jobid); + } + + if (p == 0) { + char tmp[50]; + sprintf(tmp, "Job %i not finished or not running.\n", jobid); + send_list_line(s, tmp); + return; + } + cmd = (char *)malloc(strlen(p->command) + 1); + sprintf(cmd, "%s\n", p->command); + send_list_line(s, cmd); + free(cmd); } void s_mark_job_running(int jobid) { - struct Job *p; - p = findjob(jobid); - if (!p) - error("Cannot mark the jobid %i RUNNING.", jobid); - p->state = RUNNING; + struct Job *p; + p = findjob(jobid); + if (!p) + error("Cannot mark the jobid %i RUNNING.", jobid); + p->state = RUNNING; } /* -1 means nothing awaken, otherwise returns the jobid awaken */ int wake_hold_client() { - struct Job *p; - p = findjob_holding_client(); - if (p) { - p->state = QUEUED; - return p->jobid; - } - return -1; + struct Job *p; + p = findjob_holding_client(); + if (p) { + p->state = QUEUED; + return p->jobid; + } + return -1; } const char *jstate2string(enum Jobstate s) { - const char *jobstate; - switch (s) { - case QUEUED: - jobstate = "queued"; - break; - case RUNNING: - jobstate = "running"; - break; - case FINISHED: - jobstate = "finished"; - break; - case SKIPPED: - case HOLDING_CLIENT: - jobstate = "skipped"; - break; - } - return jobstate; + const char *jobstate; + switch (s) { + case QUEUED: + jobstate = "queued"; + break; + case RUNNING: + jobstate = "running"; + break; + case FINISHED: + jobstate = "finished"; + break; + case SKIPPED: + case HOLDING_CLIENT: + jobstate = "skipped"; + break; + } + return jobstate; } void s_list(int s) { - struct Job *p; - char *buffer; - - /* Times: 0.00/0.00/0.00 - 4+4+4+2 = 14*/ - buffer = joblist_headers(); - send_list_line(s, buffer); - free(buffer); - - /* Show Queued or Running jobs */ - p = firstjob; - while (p != 0) { - if (p->state != HOLDING_CLIENT) { - buffer = joblist_line(p); - send_list_line(s, buffer); - free(buffer); - } - p = p->next; + struct Job *p; + char *buffer; + + /* Times: 0.00/0.00/0.00 - 4+4+4+2 = 14*/ + buffer = joblist_headers(); + send_list_line(s, buffer); + free(buffer); + + /* Show Queued or Running jobs */ + p = firstjob; + while (p != 0) { + if (p->state != HOLDING_CLIENT) { + buffer = joblist_line(p); + send_list_line(s, buffer); + free(buffer); } + p = p->next; + } - p = first_finished_job; + p = first_finished_job; - /* Show Finished jobs */ - while (p != 0) { - buffer = joblist_line(p); - send_list_line(s, buffer); - free(buffer); - p = p->next; - } + /* Show Finished jobs */ + while (p != 0) { + buffer = joblist_line(p); + send_list_line(s, buffer); + free(buffer); + p = p->next; + } } void s_list_plain(int s) { - struct Job *p; - char *buffer; - - /* Show Queued or Running jobs */ - p = firstjob; - while (p != 0) { - if (p->state != HOLDING_CLIENT) { - buffer = joblist_line_plain(p); - send_list_line(s, buffer); - free(buffer); - } - p = p->next; + struct Job *p; + char *buffer; + + /* Show Queued or Running jobs */ + p = firstjob; + while (p != 0) { + if (p->state != HOLDING_CLIENT) { + buffer = joblist_line_plain(p); + send_list_line(s, buffer); + free(buffer); } + p = p->next; + } - p = first_finished_job; + p = first_finished_job; - /* Show Finished jobs */ - while (p != 0) { - buffer = joblist_line_plain(p); - send_list_line(s, buffer); - free(buffer); - p = p->next; - } + /* Show Finished jobs */ + while (p != 0) { + buffer = joblist_line_plain(p); + send_list_line(s, buffer); + free(buffer); + p = p->next; + } } static struct Job *newjobptr() { - struct Job *p; - - if (firstjob == 0) { - firstjob = (struct Job *) malloc(sizeof(*firstjob)); - firstjob->next = 0; - firstjob->output_filename = 0; - firstjob->command = 0; - return firstjob; - } + struct Job *p; - p = firstjob; - while (p->next != 0) - p = p->next; + if (firstjob == 0) { + firstjob = (struct Job *)malloc(sizeof(*firstjob)); + firstjob->next = 0; + firstjob->output_filename = 0; + firstjob->command = 0; + return firstjob; + } + + p = firstjob; + while (p->next != 0) + p = p->next; - p->next = (struct Job *) malloc(sizeof(*p)); - p->next->next = 0; - p->next->output_filename = 0; - p->next->command = 0; + p->next = (struct Job *)malloc(sizeof(*p)); + p->next->next = 0; + p->next->output_filename = 0; + p->next->command = 0; - return p->next; + return p->next; } /* Returns -1 if no last job id found */ static int find_last_jobid_in_queue(int neglect_jobid) { - struct Job *p; - int last_jobid = -1; + struct Job *p; + int last_jobid = -1; - p = firstjob; - while (p != 0) { - if (p->jobid != neglect_jobid && - p->jobid > last_jobid) - last_jobid = p->jobid; - p = p->next; - } + p = firstjob; + while (p != 0) { + if (p->jobid != neglect_jobid && p->jobid > last_jobid) + last_jobid = p->jobid; + p = p->next; + } - return last_jobid; + return last_jobid; } /* Returns -1 if no last job id found */ static int find_last_stored_jobid_finished() { - struct Job *p; - int last_jobid = -1; + struct Job *p; + int last_jobid = -1; - p = first_finished_job; - while (p != 0) { - if (p->jobid > last_jobid) - last_jobid = p->jobid; - p = p->next; - } + p = first_finished_job; + while (p != 0) { + if (p->jobid > last_jobid) + last_jobid = p->jobid; + p = p->next; + } - return last_jobid; + return last_jobid; } /* Returns job id or -1 on error */ int s_newjob(int s, struct Msg *m) { - struct Job *p; - int res; - - p = newjobptr(); - - p->jobid = jobids++; - if (count_not_finished_jobs() < max_jobs) - p->state = QUEUED; - else - p->state = HOLDING_CLIENT; - p->num_slots = m->u.newjob.num_slots; - p->store_output = m->u.newjob.store_output; - p->should_keep_finished = m->u.newjob.should_keep_finished; - p->notify_errorlevel_to = 0; - p->notify_errorlevel_to_size = 0; - p->depend_on_size = m->u.newjob.depend_on_size; - p->depend_on = 0; - - /* this error level here is used internally to decide whether a job should be run or not - * so it only matters whether the error level is 0 or not. - * thus, summing the absolute error levels of all dependencies is sufficient.*/ - p->dependency_errorlevel = 0; - if (m->u.newjob.depend_on_size) { - int *depend_on; - int foo; - depend_on = recv_ints(s, &foo); - assert(p->depend_on_size == foo); - - /* Depend on the last queued job. */ - int idx = 0; - for (int i = 0; i < p->depend_on_size; i++) { - /* filter out dependencies that are current jobs */ - if (depend_on[i] >= p->jobid) - continue; - - p->depend_on = (int*) realloc(p->depend_on, (idx + 1) * sizeof(int)); - /* As we already have 'p' in the queue, - * neglect it during the find_last_jobid_in_queue() */ - if (depend_on[i] == -1) { - p->depend_on[idx] = find_last_jobid_in_queue(p->jobid); - - /* We don't trust the last jobid in the queue (running or queued) - * if it's not the last added job. In that case, let - * the next control flow handle it as if it could not - * do_depend on any still queued job. */ - if (last_finished_jobid > p->depend_on[idx]) - p->depend_on[idx] = -1; - - /* If it's queued still without result, let it know - * its result to p when it finishes. */ - if (p->depend_on[idx] != -1) { - struct Job *depended_job; - depended_job = findjob(p->depend_on[idx]); - if (depended_job != 0) - add_notify_errorlevel_to(depended_job, p->jobid); - else - warning("The jobid %i is queued to do_depend on the jobid %i" - " suddenly non existent in the queue", p->jobid, - p->depend_on[idx]); - } else /* Otherwise take the finished job, or the last_errorlevel */ - { - if (depend_on[i] == -1) { - int ljobid = find_last_stored_jobid_finished(); - p->depend_on[idx] = ljobid; - - /* If we have a newer result stored, use it */ - /* NOTE: - * Reading this now, I don't know how ljobid can be - * greater than last_finished_jobid */ - if (last_finished_jobid < ljobid) { - struct Job *parent; - parent = find_finished_job(ljobid); - if (!parent) - error("jobid %i suddenly disappeared from the finished list", - ljobid); - p->dependency_errorlevel += abs(parent->result.errorlevel); - } else - p->dependency_errorlevel += abs(last_errorlevel); - } - } - } else { - /* The user decided what's the job this new job depends on */ - struct Job *depended_job; - p->depend_on[idx] = depend_on[i]; - depended_job = findjob(p->depend_on[idx]); - - if (depended_job != 0) - add_notify_errorlevel_to(depended_job, p->jobid); - else { - struct Job *parent; - parent = find_finished_job(p->depend_on[idx]); - if (parent) { - p->dependency_errorlevel += abs(parent->result.errorlevel); - } else { - /* We consider as if the job not found - didn't finish well */ - p->dependency_errorlevel += 1; - } - } - } - idx++; + struct Job *p; + int res; + + p = newjobptr(); + + p->jobid = jobids++; + if (count_not_finished_jobs() < max_jobs) + p->state = QUEUED; + else + p->state = HOLDING_CLIENT; + + // save the user_id and record the number of waiting jobs + p->user_id = get_user_id(m->uid); + user_queue[p->user_id]++; + + p->num_slots = m->u.newjob.num_slots; + p->store_output = m->u.newjob.store_output; + p->should_keep_finished = m->u.newjob.should_keep_finished; + p->notify_errorlevel_to = 0; + p->notify_errorlevel_to_size = 0; + p->depend_on_size = m->u.newjob.depend_on_size; + p->depend_on = 0; + + /* this error level here is used internally to decide whether a job should be + * run or not so it only matters whether the error level is 0 or not. thus, + * summing the absolute error levels of all dependencies is sufficient.*/ + p->dependency_errorlevel = 0; + if (m->u.newjob.depend_on_size) { + int *depend_on; + int foo; + depend_on = recv_ints(s, &foo); + assert(p->depend_on_size == foo); + + /* Depend on the last queued job. */ + int idx = 0; + for (int i = 0; i < p->depend_on_size; i++) { + /* filter out dependencies that are current jobs */ + if (depend_on[i] >= p->jobid) + continue; + + p->depend_on = (int *)realloc(p->depend_on, (idx + 1) * sizeof(int)); + /* As we already have 'p' in the queue, + * neglect it during the find_last_jobid_in_queue() */ + if (depend_on[i] == -1) { + p->depend_on[idx] = find_last_jobid_in_queue(p->jobid); + + /* We don't trust the last jobid in the queue (running or queued) + * if it's not the last added job. In that case, let + * the next control flow handle it as if it could not + * do_depend on any still queued job. */ + if (last_finished_jobid > p->depend_on[idx]) + p->depend_on[idx] = -1; + + /* If it's queued still without result, let it know + * its result to p when it finishes. */ + if (p->depend_on[idx] != -1) { + struct Job *depended_job; + depended_job = findjob(p->depend_on[idx]); + if (depended_job != 0) + add_notify_errorlevel_to(depended_job, p->jobid); + else + warning("The jobid %i is queued to do_depend on the jobid %i" + " suddenly non existent in the queue", + p->jobid, p->depend_on[idx]); + } else /* Otherwise take the finished job, or the last_errorlevel */ + { + if (depend_on[i] == -1) { + int ljobid = find_last_stored_jobid_finished(); + p->depend_on[idx] = ljobid; + + /* If we have a newer result stored, use it */ + /* NOTE: + * Reading this now, I don't know how ljobid can be + * greater than last_finished_jobid */ + if (last_finished_jobid < ljobid) { + struct Job *parent; + parent = find_finished_job(ljobid); + if (!parent) + error("jobid %i suddenly disappeared from the finished list", + ljobid); + p->dependency_errorlevel += abs(parent->result.errorlevel); + } else + p->dependency_errorlevel += abs(last_errorlevel); + } } - free(depend_on); - p->depend_on_size = idx; + } else { + /* The user decided what's the job this new job depends on */ + struct Job *depended_job; + p->depend_on[idx] = depend_on[i]; + depended_job = findjob(p->depend_on[idx]); + + if (depended_job != 0) + add_notify_errorlevel_to(depended_job, p->jobid); + else { + struct Job *parent; + parent = find_finished_job(p->depend_on[idx]); + if (parent) { + p->dependency_errorlevel += abs(parent->result.errorlevel); + } else { + /* We consider as if the job not found + didn't finish well */ + p->dependency_errorlevel += 1; + } + } + } + idx++; } + free(depend_on); + p->depend_on_size = idx; + } - /* if dependency list is empty after removing invalid dependencies, make it independent */ - if (p->depend_on_size == 0) - p->depend_on = 0; - - pinfo_init(&p->info); - pinfo_set_enqueue_time(&p->info); + /* if dependency list is empty after removing invalid dependencies, make it + * independent */ + if (p->depend_on_size == 0) + p->depend_on = 0; - /* load the command */ - p->command = malloc(m->u.newjob.command_size); - if (p->command == 0) - error("Cannot allocate memory in s_newjob command_size (%i)", - m->u.newjob.command_size); - res = recv_bytes(s, p->command, m->u.newjob.command_size); + pinfo_init(&p->info); + pinfo_set_enqueue_time(&p->info); + + /* load the command */ + p->command = malloc(m->u.newjob.command_size); + if (p->command == 0) + error("Cannot allocate memory in s_newjob command_size (%i)", + m->u.newjob.command_size); + res = recv_bytes(s, p->command, m->u.newjob.command_size); + if (res == -1) + error("wrong bytes received"); + + /* load the label */ + p->label = 0; + if (m->u.newjob.label_size > 0) { + char *ptr; + ptr = (char *)malloc(m->u.newjob.label_size); + if (ptr == 0) + error("Cannot allocate memory in s_newjob label_size(%i)", + m->u.newjob.label_size); + res = recv_bytes(s, ptr, m->u.newjob.label_size); if (res == -1) - error("wrong bytes received"); - - /* load the label */ - p->label = 0; - if (m->u.newjob.label_size > 0) { - char *ptr; - ptr = (char *) malloc(m->u.newjob.label_size); - if (ptr == 0) - error("Cannot allocate memory in s_newjob label_size(%i)", - m->u.newjob.label_size); - res = recv_bytes(s, ptr, m->u.newjob.label_size); - if (res == -1) - error("wrong bytes received"); - p->label = ptr; - } - - /* load the info */ - if (m->u.newjob.env_size > 0) { - char *ptr; - ptr = (char *) malloc(m->u.newjob.env_size); - if (ptr == 0) - error("Cannot allocate memory in s_newjob env_size(%i)", - m->u.newjob.env_size); - res = recv_bytes(s, ptr, m->u.newjob.env_size); - if (res == -1) - error("wrong bytes received"); - pinfo_addinfo(&p->info, m->u.newjob.env_size + 100, - "Environment:\n%s", ptr); - free(ptr); - } - return p->jobid; + error("wrong bytes received"); + p->label = ptr; + } + + /* load the info */ + if (m->u.newjob.env_size > 0) { + char *ptr; + ptr = (char *)malloc(m->u.newjob.env_size); + if (ptr == 0) + error("Cannot allocate memory in s_newjob env_size(%i)", + m->u.newjob.env_size); + res = recv_bytes(s, ptr, m->u.newjob.env_size); + if (res == -1) + error("wrong bytes received"); + pinfo_addinfo(&p->info, m->u.newjob.env_size + 100, "Environment:\n%s", + ptr); + free(ptr); + } + return p->jobid; } /* This assumes the jobid exists */ void s_removejob(int jobid) { - struct Job *p; - struct Job *newnext; + struct Job *p; + struct Job *newnext; - if (firstjob->jobid == jobid) { - struct Job *newfirst; + if (firstjob->jobid == jobid) { + struct Job *newfirst; - /* First job is to be removed */ - newfirst = firstjob->next; - destroy_job(firstjob); - firstjob = newfirst; - return; - } + /* First job is to be removed */ + newfirst = firstjob->next; + destroy_job(firstjob); + firstjob = newfirst; + return; + } - p = firstjob; - /* Not first job */ - while (p->next != 0) { - if (p->next->jobid == jobid) - break; - p = p->next; - } - if (p->next == 0) - error("Job to be removed not found. jobid=%i", jobid); + p = firstjob; + /* Not first job */ + while (p->next != 0) { + if (p->next->jobid == jobid) + break; + p = p->next; + } + if (p->next == 0) + error("Job to be removed not found. jobid=%i", jobid); - newnext = p->next->next; + newnext = p->next->next; - destroy_job(p->next); - p->next = newnext; + destroy_job(p->next); + p->next = newnext; } /* -1 if no one should be run. */ int next_run_job() { - struct Job *p; + struct Job *p; + static int uid = 0; + uid = (uid + 1) % user_number; - const int free_slots = max_slots - busy_slots; + const int free_slots = max_slots - busy_slots; - /* busy_slots may be bigger than the maximum slots, - * if the user was running many jobs, and suddenly - * trimmed the maximum slots down. */ - if (free_slots <= 0) - return -1; + /* busy_slots may be bigger than the maximum slots, + * if the user was running many jobs, and suddenly + * trimmed the maximum slots down. */ + if (free_slots <= 0) + return -1; - /* If there are no jobs to run... */ - if (firstjob == 0) - return -1; + /* If there are no jobs to run... */ + if (firstjob == 0) + return -1; - /* Look for a runnable task */ - p = firstjob; - while (p != 0) { - if (p->state == QUEUED) { - if (p->depend_on_size) { - int ready = 1; - for (int i = 0; i < p->depend_on_size; i++) { - struct Job *do_depend_job = get_job(p->depend_on[i]); - /* We won't try to run any job do_depending on an unfinished - * job */ - if (do_depend_job != NULL && - (do_depend_job->state == QUEUED || do_depend_job->state == RUNNING)) { - /* Next try */ - p = p->next; - ready = 0; - break; - } - } - if (ready != 1) - continue; - } - - if (free_slots >= p->num_slots) { - busy_slots = busy_slots + p->num_slots; - return p->jobid; - } + /* Look for a runnable task */ + p = firstjob; + while (p != 0) { + if (p->state == QUEUED) { + if (p->depend_on_size) { + int ready = 1; + for (int i = 0; i < p->depend_on_size; i++) { + struct Job *do_depend_job = get_job(p->depend_on[i]); + /* We won't try to run any job do_depending on an unfinished + * job */ + if (do_depend_job != NULL && (do_depend_job->state == QUEUED || + do_depend_job->state == RUNNING)) { + /* Next try */ + p = p->next; + ready = 0; + break; + } } - p = p->next; + if (ready != 1) + continue; + } + + int num_slots = p->num_slots, id = p->user_id; + if (id == uid && free_slots >= num_slots && + user_max_slots[id] - user_busy[id] >= num_slots) { + busy_slots = busy_slots + num_slots; + user_busy[id] += num_slots; + user_jobs[id]++; + user_queue[id]--; + return p->jobid; + } } - - return -1; + p = p->next; + } + return -1; } /* Returns 1000 if no limit, The limit otherwise. */ static int get_max_finished_jobs() { - char *limit; + char *limit; - limit = getenv("TS_MAXFINISHED"); - if (limit == NULL) - return 1000; - return abs(atoi(limit)); + limit = getenv("TS_MAXFINISHED"); + if (limit == NULL) + return 1000; + return abs(atoi(limit)); } /* Add the job to the finished queue. */ static void new_finished_job(struct Job *j) { - struct Job *p; - int count, max; + struct Job *p; + int count, max; - max = get_max_finished_jobs(); - count = 0; + max = get_max_finished_jobs(); + count = 0; - if (first_finished_job == 0 && count < max) { - first_finished_job = j; - first_finished_job->next = 0; - return; - } + if (first_finished_job == 0 && count < max) { + first_finished_job = j; + first_finished_job->next = 0; + return; + } - ++count; + ++count; - p = first_finished_job; - while (p->next != 0) { - p = p->next; - ++count; - } + p = first_finished_job; + while (p->next != 0) { + p = p->next; + ++count; + } - /* If too many jobs, wipe out the first */ - if (count >= max) { - struct Job *tmp; - tmp = first_finished_job; - first_finished_job = first_finished_job->next; - destroy_job(tmp); - } - p->next = j; - p->next->next = 0; + /* If too many jobs, wipe out the first */ + if (count >= max) { + struct Job *tmp; + tmp = first_finished_job; + first_finished_job = first_finished_job->next; + destroy_job(tmp); + } + p->next = j; + p->next->next = 0; } static int job_is_in_state(int jobid, enum Jobstate state) { - struct Job *p; + struct Job *p; - p = findjob(jobid); - if (p == 0) - return 0; - if (p->state == state) - return 1; + p = findjob(jobid); + if (p == 0) return 0; + if (p->state == state) + return 1; + return 0; } -int job_is_running(int jobid) { - return job_is_in_state(jobid, RUNNING); -} +int job_is_running(int jobid) { return job_is_in_state(jobid, RUNNING); } int job_is_holding_client(int jobid) { - return job_is_in_state(jobid, HOLDING_CLIENT); + return job_is_in_state(jobid, HOLDING_CLIENT); } static int in_notify_list(int jobid) { - struct Notify *n, *tmp; - - n = first_notify; - while (n != 0) { - tmp = n; - n = n->next; - if (tmp->jobid == jobid) - return 1; - } - return 0; + struct Notify *n, *tmp; + + n = first_notify; + while (n != 0) { + tmp = n; + n = n->next; + if (tmp->jobid == jobid) + return 1; + } + return 0; } void job_finished(const struct Result *result, int jobid) { - struct Job *p; - - if (busy_slots <= 0) - error("Wrong state in the server. busy_slots = %i instead of greater than 0", busy_slots); - - p = findjob(jobid); - if (p == 0) - error("on jobid %i finished, it doesn't exist", jobid); - - /* The job may be not only in running state, but also in other states, as - * we call this to clean up the jobs list in case of the client closing the - * connection. */ - if (p->state == RUNNING) - busy_slots = busy_slots - p->num_slots; - - /* Mark state */ - if (result->skipped) - p->state = SKIPPED; - else - p->state = FINISHED; - p->result = *result; - last_finished_jobid = p->jobid; - notify_errorlevel(p); - pinfo_set_end_time(&p->info); - - if (p->result.died_by_signal) - pinfo_addinfo(&p->info, 100, "Exit status: killed by signal %i\n", p->result.signal); - else - pinfo_addinfo(&p->info, 100, "Exit status: died with exit code %i\n", p->result.errorlevel); - - /* Find the pointing node, to - * update it removing the finished job. */ - { - struct Job **jpointer = 0; - struct Job *newfirst = p->next; - if (firstjob == p) - jpointer = &firstjob; - else { - struct Job *p2; - p2 = firstjob; - while (p2 != 0) { - if (p2->next == p) { - jpointer = &(p2->next); - break; - } - p2 = p2->next; - } + struct Job *p; + + if (busy_slots <= 0) + error( + "Wrong state in the server. busy_slots = %i instead of greater than 0", + busy_slots); + + p = findjob(jobid); + if (p == 0) + error("on jobid %i finished, it doesn't exist", jobid); + + /* The job may be not only in running state, but also in other states, as + * we call this to clean up the jobs list in case of the client closing the + * connection. */ + if (p->state == RUNNING) { + busy_slots = busy_slots - p->num_slots; + user_busy[p->user_id] -= p->num_slots; + user_jobs[p->user_id]--; + } + + /* Mark state */ + if (result->skipped) + p->state = SKIPPED; + else + p->state = FINISHED; + p->result = *result; + last_finished_jobid = p->jobid; + notify_errorlevel(p); + pinfo_set_end_time(&p->info); + + if (p->result.died_by_signal) + pinfo_addinfo(&p->info, 100, "Exit status: killed by signal %i\n", + p->result.signal); + else + pinfo_addinfo(&p->info, 100, "Exit status: died with exit code %i\n", + p->result.errorlevel); + + /* Find the pointing node, to + * update it removing the finished job. */ + { + struct Job **jpointer = 0; + struct Job *newfirst = p->next; + if (firstjob == p) + jpointer = &firstjob; + else { + struct Job *p2; + p2 = firstjob; + while (p2 != 0) { + if (p2->next == p) { + jpointer = &(p2->next); + break; } + p2 = p2->next; + } + } - /* Add it to the finished queue (maybe temporarily) */ - if (p->should_keep_finished || in_notify_list(p->jobid)) - new_finished_job(p); + /* Add it to the finished queue (maybe temporarily) */ + if (p->should_keep_finished || in_notify_list(p->jobid)) + new_finished_job(p); - /* Remove it from the run queue */ - if (jpointer == 0) - error("Cannot remove a finished job from the " - "queue list (jobid=%i)", p->jobid); + /* Remove it from the run queue */ + if (jpointer == 0) + error("Cannot remove a finished job from the " + "queue list (jobid=%i)", + p->jobid); - *jpointer = newfirst; - } + *jpointer = newfirst; + } } void s_clear_finished() { - struct Job *p; + struct Job *p; - if (first_finished_job == 0) - return; + if (first_finished_job == 0) + return; - p = first_finished_job; - first_finished_job = 0; + p = first_finished_job; + first_finished_job = 0; - while (p != 0) { - struct Job *tmp; - tmp = p->next; - destroy_job(p); - p = tmp; - } + while (p != 0) { + struct Job *tmp; + tmp = p->next; + destroy_job(p); + p = tmp; + } } void s_process_runjob_ok(int jobid, char *oname, int pid) { - struct Job *p; - p = findjob(jobid); - if (p == 0) - error("Job %i already run not found on runjob_ok", jobid); - if (p->state != RUNNING) - error("Job %i not running, but %i on runjob_ok", jobid, - p->state); - - p->pid = pid; - p->output_filename = oname; - pinfo_set_start_time(&p->info); + struct Job *p; + p = findjob(jobid); + if (p == 0) + error("Job %i already run not found on runjob_ok", jobid); + if (p->state != RUNNING) + error("Job %i not running, but %i on runjob_ok", jobid, p->state); + + p->pid = pid; + p->output_filename = oname; + pinfo_set_start_time(&p->info); } void s_send_runjob(int s, int jobid) { - struct Msg m = default_msg(); - struct Job *p; + struct Msg m = default_msg(); + struct Job *p; - p = findjob(jobid); - if (p == 0) - error("Job %i was expected to run", jobid); + p = findjob(jobid); + if (p == 0) + error("Job %i was expected to run", jobid); - m.type = RUNJOB; + m.type = RUNJOB; - /* TODO - * We should make the dependencies update the jobids they're do_depending on. - * Then, on finish, these could set the errorlevel to send to its dependency childs. - * We cannot consider that the jobs will leave traces in the finished job list (-nf?) . */ + /* TODO + * We should make the dependencies update the jobids they're do_depending on. + * Then, on finish, these could set the errorlevel to send to its dependency + * childs. + * We cannot consider that the jobs will leave traces in the finished job list + * (-nf?) . */ - m.u.last_errorlevel = p->dependency_errorlevel; - send_msg(s, &m); + m.u.last_errorlevel = p->dependency_errorlevel; + send_msg(s, &m); } void s_job_info(int s, int jobid) { - struct Job *p = 0; - struct Msg m = default_msg(); - - if (jobid == -1) { - /* This means that we want the job info of the running task, or that - * of the last job run */ - if (busy_slots > 0) { - p = firstjob; - if (p == 0) - error("Internal state WAITING, but job not run." - "firstjob = %x", firstjob); - } else { - p = first_finished_job; - if (p == 0) { - send_list_line(s, "No jobs.\n"); - return; - } - while (p->next != 0) - p = p->next; - } + struct Job *p = 0; + struct Msg m = default_msg(); + + if (jobid == -1) { + /* This means that we want the job info of the running task, or that + * of the last job run */ + if (busy_slots > 0) { + p = firstjob; + if (p == 0) + error("Internal state WAITING, but job not run." + "firstjob = %x", + firstjob); } else { - p = firstjob; - while (p != 0 && p->jobid != jobid) - p = p->next; - - /* Look in finished jobs if needed */ - if (p == 0) { - p = first_finished_job; - while (p != 0 && p->jobid != jobid) - p = p->next; - } - } - - if (p == 0) { - char tmp[50]; - sprintf(tmp, "Job %i not finished or not running.\n", jobid); - send_list_line(s, tmp); + p = first_finished_job; + if (p == 0) { + send_list_line(s, "No jobs.\n"); return; + } + while (p->next != 0) + p = p->next; } + } else { + p = firstjob; + while (p != 0 && p->jobid != jobid) + p = p->next; - m.type = INFO_DATA; - send_msg(s, &m); - pinfo_dump(&p->info, s); - fd_nprintf(s, 100, "Command: "); - if (p->depend_on) { - fd_nprintf(s, 100, "[%i,", p->depend_on[0]); - for (int i = 1; i < p->depend_on_size; i++) - fd_nprintf(s, 100, ",%i", p->depend_on[i]); - fd_nprintf(s, 100, "]&& "); - } - write(s, p->command, strlen(p->command)); - fd_nprintf(s, 100, "\n"); - fd_nprintf(s, 100, "Slots required: %i\n", p->num_slots); - fd_nprintf(s, 100, "Enqueue time: %s", - ctime(&p->info.enqueue_time.tv_sec)); - if (p->state == RUNNING) { - fd_nprintf(s, 100, "Start time: %s", - ctime(&p->info.start_time.tv_sec)); - float t = pinfo_time_until_now(&p->info); - char *unit = time_rep(&t); - fd_nprintf(s, 100, "Time running: %f%s\n", t, unit); - } else if (p->state == FINISHED) { - fd_nprintf(s, 100, "Start time: %s", - ctime(&p->info.start_time.tv_sec)); - fd_nprintf(s, 100, "End time: %s", - ctime(&p->info.end_time.tv_sec)); - float t = pinfo_time_run(&p->info); - char *unit = time_rep(&t); - fd_nprintf(s, 100, "Time run: %f%s\n", t, unit); + /* Look in finished jobs if needed */ + if (p == 0) { + p = first_finished_job; + while (p != 0 && p->jobid != jobid) + p = p->next; } + } + + if (p == 0) { + char tmp[50]; + sprintf(tmp, "Job %i not finished or not running.\n", jobid); + send_list_line(s, tmp); + return; + } + + m.type = INFO_DATA; + send_msg(s, &m); + pinfo_dump(&p->info, s); + fd_nprintf(s, 100, "Command: "); + if (p->depend_on) { + fd_nprintf(s, 100, "[%i,", p->depend_on[0]); + for (int i = 1; i < p->depend_on_size; i++) + fd_nprintf(s, 100, ",%i", p->depend_on[i]); + fd_nprintf(s, 100, "]&& "); + } + write(s, p->command, strlen(p->command)); + fd_nprintf(s, 100, "\n"); + fd_nprintf(s, 100, "Slots required: %i\n", p->num_slots); + fd_nprintf(s, 100, "Enqueue time: %s", ctime(&p->info.enqueue_time.tv_sec)); + if (p->state == RUNNING) { + fd_nprintf(s, 100, "Start time: %s", ctime(&p->info.start_time.tv_sec)); + float t = pinfo_time_until_now(&p->info); + char *unit = time_rep(&t); + fd_nprintf(s, 100, "Time running: %f%s\n", t, unit); + } else if (p->state == FINISHED) { + fd_nprintf(s, 100, "Start time: %s", ctime(&p->info.start_time.tv_sec)); + fd_nprintf(s, 100, "End time: %s", ctime(&p->info.end_time.tv_sec)); + float t = pinfo_time_run(&p->info); + char *unit = time_rep(&t); + fd_nprintf(s, 100, "Time run: %f%s\n", t, unit); + } + fd_nprintf(s, 100, "extralines\n"); } void s_send_last_id(int s) { - struct Msg m = default_msg(); + struct Msg m = default_msg(); - m.type = LAST_ID; - m.u.jobid = jobids - 1; - send_msg(s, &m); + m.type = LAST_ID; + m.u.jobid = jobids - 1; + send_msg(s, &m); } void s_send_output(int s, int jobid) { - struct Job *p = 0; - struct Msg m = default_msg(); - - if (jobid == -1) { - /* This means that we want the output info of the running task, or that - * of the last job run */ - if (busy_slots > 0) { - p = firstjob; - if (p == 0) - error("Internal state WAITING, but job not run." - "firstjob = %x", firstjob); - } else { - p = first_finished_job; - if (p == 0) { - send_list_line(s, "No jobs.\n"); - return; - } - while (p->next != 0) - p = p->next; - } + struct Job *p = 0; + struct Msg m = default_msg(); + + if (jobid == -1) { + /* This means that we want the output info of the running task, or that + * of the last job run */ + if (busy_slots > 0) { + p = firstjob; + if (p == 0) + error("Internal state WAITING, but job not run." + "firstjob = %x", + firstjob); } else { - p = get_job(jobid); - if (p != 0 && p->state != RUNNING - && p->state != FINISHED - && p->state != SKIPPED) - p = 0; - } - - if (p == 0) { - char tmp[50]; - if (jobid == -1) - sprintf(tmp, "The last job has not finished or is not running.\n"); - else - sprintf(tmp, "Job %i not finished or not running.\n", jobid); - send_list_line(s, tmp); + p = first_finished_job; + if (p == 0) { + send_list_line(s, "No jobs.\n"); return; + } + while (p->next != 0) + p = p->next; } + } else { + p = get_job(jobid); + if (p != 0 && p->state != RUNNING && p->state != FINISHED && + p->state != SKIPPED) + p = 0; + } + + if (p == 0) { + char tmp[50]; + if (jobid == -1) + sprintf(tmp, "The last job has not finished or is not running.\n"); + else + sprintf(tmp, "Job %i not finished or not running.\n", jobid); + send_list_line(s, tmp); + return; + } - if (p->state == SKIPPED) { - char tmp[50]; - if (jobid == -1) - sprintf(tmp, "The last job was skipped due to a dependency.\n"); + if (p->state == SKIPPED) { + char tmp[50]; + if (jobid == -1) + sprintf(tmp, "The last job was skipped due to a dependency.\n"); - else - sprintf(tmp, "Job %i was skipped due to a dependency.\n", jobid); - send_list_line(s, tmp); - return; - } - - m.type = ANSWER_OUTPUT; - m.u.output.store_output = p->store_output; - m.u.output.pid = p->pid; - if (m.u.output.store_output && p->output_filename) - m.u.output.ofilename_size = strlen(p->output_filename) + 1; else - m.u.output.ofilename_size = 0; - send_msg(s, &m); - if (m.u.output.ofilename_size > 0) - send_bytes(s, p->output_filename, m.u.output.ofilename_size); + sprintf(tmp, "Job %i was skipped due to a dependency.\n", jobid); + send_list_line(s, tmp); + return; + } + + m.type = ANSWER_OUTPUT; + m.u.output.store_output = p->store_output; + m.u.output.pid = p->pid; + if (m.u.output.store_output && p->output_filename) + m.u.output.ofilename_size = strlen(p->output_filename) + 1; + else + m.u.output.ofilename_size = 0; + send_msg(s, &m); + if (m.u.output.ofilename_size > 0) + send_bytes(s, p->output_filename, m.u.output.ofilename_size); } void notify_errorlevel(struct Job *p) { - int i; + int i; - last_errorlevel = p->result.errorlevel; + last_errorlevel = p->result.errorlevel; - for (i = 0; i < p->notify_errorlevel_to_size; ++i) { - struct Job *notified; - notified = get_job(p->notify_errorlevel_to[i]); - if (notified) { - notified->dependency_errorlevel += abs(p->result.errorlevel); - } + for (i = 0; i < p->notify_errorlevel_to_size; ++i) { + struct Job *notified; + notified = get_job(p->notify_errorlevel_to[i]); + if (notified) { + notified->dependency_errorlevel += abs(p->result.errorlevel); } + } } /* jobid is input/output. If the input is -1, it's changed to the jobid * removed */ -int s_remove_job(int s, int *jobid) { - struct Job *p = 0; - struct Msg m = default_msg(); - struct Job *before_p = 0; - - if (*jobid == -1) { - /* Find the last job added */ - p = firstjob; - if (p != 0) { - while (p->next != 0) { - before_p = p; - p = p->next; - } - } else { - /* last 'finished' */ - p = first_finished_job; - if (p) { - while (p->next != 0) { - before_p = p; - p = p->next; - } - } - } +int s_remove_job(int s, int *jobid, int client_uid) { + struct Job *p = 0; + struct Msg m = default_msg(); + struct Job *before_p = 0; + + if (*jobid == -1) { + /* Find the last job added */ + p = firstjob; + if (p != 0) { + while (p->next != 0) { + before_p = p; + p = p->next; + } } else { - p = firstjob; - if (p != 0) { - while (p->next != 0 && p->jobid != *jobid) { - before_p = p; - p = p->next; - } + /* last 'finished' */ + p = first_finished_job; + if (p) { + while (p->next != 0) { + before_p = p; + p = p->next; } + } + } + } else { + p = firstjob; + if (p != 0) { + while (p->next != 0 && p->jobid != *jobid) { + before_p = p; + p = p->next; + } + } - /* If not found, look in the 'finished' list */ - if (p == 0 || p->jobid != *jobid) { - p = first_finished_job; - if (p != 0) { - while (p->next != 0 && p->jobid != *jobid) { - before_p = p; - p = p->next; - } - if (p->jobid != *jobid) - p = 0; - } + /* If not found, look in the 'finished' list */ + if (p == 0 || p->jobid != *jobid) { + p = first_finished_job; + if (p != 0) { + while (p->next != 0 && p->jobid != *jobid) { + before_p = p; + p = p->next; } + if (p->jobid != *jobid) + p = 0; + } } + } - if (p == 0 || p->state == RUNNING || p == firstjob) { - char tmp[50]; - if (*jobid == -1) - sprintf(tmp, "The last job cannot be removed.\n"); - else - sprintf(tmp, "The job %i cannot be removed.\n", *jobid); - send_list_line(s, tmp); - return 0; - } + // if (p == NULL || p == firstjob || user_UID[p->user_id] != client_uid) { + if (p == NULL || p == firstjob || user_UID[p->user_id] != client_uid) { + + char tmp[256]; - /* Return the jobid found */ - *jobid = p->jobid; + if (*jobid == -1) + snprintf(tmp, 256, "The last job cannot be removed.\n"); + else + snprintf(tmp, 256, "The job %i cannot be removed.\n", *jobid); + if (p == firstjob && p->state == RUNNING) { + kill(p->pid, SIGTERM); + snprintf(tmp, 256, "The first job %i is removed.\n", *jobid); + } + if (p != NULL) { + int id = p->user_id; + if (user_UID[id] != client_uid) { + snprintf(tmp, 256, "The job %i belongs to user:%s.\n", *jobid, + user_name[id]); + } + } + send_list_line(s, tmp); + return 0; + } - /* Tricks for the check_notify_list */ + if (p->state == RUNNING) { + kill(p->pid, SIGTERM); + } + /* + if (p == firstjob) { p->state = FINISHED; - p->result.errorlevel = -1; + send_list_line(s, "remove the first job\n"); notify_errorlevel(p); + destroy_job(p); + return 0; + } + */ + /* Return the jobid found */ + *jobid = p->jobid; - /* Notify the clients in wait_job */ - check_notify_list(m.u.jobid); + /* Tricks for the check_notify_list */ + p->state = FINISHED; + p->result.errorlevel = -1; + notify_errorlevel(p); - /* Update the list pointers */ - if (p == first_finished_job) - first_finished_job = p->next; - else - before_p->next = p->next; + /* Notify the clients in wait_job */ + check_notify_list(m.u.jobid); - destroy_job(p); + /* Update the list pointers */ + if (p == first_finished_job) + first_finished_job = p->next; + else + before_p->next = p->next; - m.type = REMOVEJOB_OK; - send_msg(s, &m); - return 1; + destroy_job(p); + + m.type = REMOVEJOB_OK; + send_msg(s, &m); + return 1; } static void add_to_notify_list(int s, int jobid) { - struct Notify *n; - struct Notify *new; + struct Notify *n; + struct Notify *new; - new = (struct Notify *) malloc(sizeof(*new)); + new = (struct Notify *)malloc(sizeof(*new)); - new->socket = s; - new->jobid = jobid; - new->next = 0; + new->socket = s; + new->jobid = jobid; + new->next = 0; - n = first_notify; - if (n == 0) { - first_notify = new; - return; - } + n = first_notify; + if (n == 0) { + first_notify = new; + return; + } - while (n->next != 0) - n = n->next; + while (n->next != 0) + n = n->next; - n->next = new; + n->next = new; } static void send_waitjob_ok(int s, int errorlevel) { - struct Msg m = default_msg(); + struct Msg m = default_msg(); - m.type = WAITJOB_OK; - m.u.result.errorlevel = errorlevel; - send_msg(s, &m); + m.type = WAITJOB_OK; + m.u.result.errorlevel = errorlevel; + send_msg(s, &m); } -static struct Job * -get_job(int jobid) { - struct Job *j; +static struct Job *get_job(int jobid) { + struct Job *j; - j = findjob(jobid); - if (j != NULL) - return j; + j = findjob(jobid); + if (j != NULL) + return j; - j = find_finished_job(jobid); + j = find_finished_job(jobid); - if (j != NULL) - return j; + if (j != NULL) + return j; - return 0; + return 0; } /* Don't complain, if the socket doesn't exist */ void s_remove_notification(int s) { - struct Notify *n; - struct Notify *previous; - n = first_notify; - while (n != 0 && n->socket != s) - n = n->next; - if (n == 0 || n->socket != s) - return; - - /* Remove the notification */ - previous = first_notify; - if (n == previous) { - first_notify = n->next; - free(n); - return; - } + struct Notify *n; + struct Notify *previous; + n = first_notify; + while (n != 0 && n->socket != s) + n = n->next; + if (n == 0 || n->socket != s) + return; + + /* Remove the notification */ + previous = first_notify; + if (n == previous) { + first_notify = n->next; + free(n); + return; + } - /* if not the first... */ - while (previous->next != n) - previous = previous->next; + /* if not the first... */ + while (previous->next != n) + previous = previous->next; - previous->next = n->next; - free(n); + previous->next = n->next; + free(n); } static void destroy_finished_job(struct Job *j) { - if (j == first_finished_job) - first_finished_job = j->next; - else { - struct Job *i; - for (i = first_finished_job; i != 0; ++i) { - if (i->next == j) { - i->next = j->next; - break; - } - } - if (i == 0) { - error("Cannot destroy the expected job %i", j->jobid); - } + if (j == first_finished_job) + first_finished_job = j->next; + else { + struct Job *i; + for (i = first_finished_job; i != 0; ++i) { + if (i->next == j) { + i->next = j->next; + break; + } } + if (i == 0) { + error("Cannot destroy the expected job %i", j->jobid); + } + } - destroy_job(j); + destroy_job(j); } /* This is called when a job finishes */ void check_notify_list(int jobid) { - struct Notify *n, *tmp; - struct Job *j; - - n = first_notify; - while (n != 0) { - tmp = n; - n = n->next; - if (tmp->jobid == jobid) { - j = get_job(jobid); - /* If the job finishes, notify the waiter */ - if (j->state == FINISHED || j->state == SKIPPED) { - send_waitjob_ok(tmp->socket, j->result.errorlevel); - /* We want to get the next Nofity* before we remove - * the actual 'n'. As s_remove_notification() simply - * removes the element from the linked list, we can - * safely follow on the list from n->next. */ - s_remove_notification(tmp->socket); - - /* Remove the jobs that were temporarily in the finished list, - * just for their notifiers. */ - if (!in_notify_list(jobid) && !j->should_keep_finished) { - destroy_finished_job(j); - } - } + struct Notify *n, *tmp; + struct Job *j; + + n = first_notify; + while (n != 0) { + tmp = n; + n = n->next; + if (tmp->jobid == jobid) { + j = get_job(jobid); + /* If the job finishes, notify the waiter */ + if (j->state == FINISHED || j->state == SKIPPED) { + send_waitjob_ok(tmp->socket, j->result.errorlevel); + /* We want to get the next Nofity* before we remove + * the actual 'n'. As s_remove_notification() simply + * removes the element from the linked list, we can + * safely follow on the list from n->next. */ + s_remove_notification(tmp->socket); + + /* Remove the jobs that were temporarily in the finished list, + * just for their notifiers. */ + if (!in_notify_list(jobid) && !j->should_keep_finished) { + destroy_finished_job(j); } + } } + } } void s_wait_job(int s, int jobid) { - struct Job *p = 0; - - if (jobid == -1) { - /* Find the last job added */ - p = firstjob; - - if (p != 0) - while (p->next != 0) - p = p->next; - - /* Look in finished jobs if needed */ - if (p == 0) { - p = first_finished_job; - if (p != 0) - while (p->next != 0) - p = p->next; - } - } else { - p = firstjob; - while (p != 0 && p->jobid != jobid) - p = p->next; + struct Job *p = 0; - /* Look in finished jobs if needed */ - if (p == 0) { - p = first_finished_job; - while (p != 0 && p->jobid != jobid) - p = p->next; - } + if (jobid == -1) { + /* Find the last job added */ + p = firstjob; + + if (p != 0) + while (p->next != 0) + p = p->next; + + /* Look in finished jobs if needed */ + if (p == 0) { + p = first_finished_job; + if (p != 0) + while (p->next != 0) + p = p->next; } + } else { + p = firstjob; + while (p != 0 && p->jobid != jobid) + p = p->next; + /* Look in finished jobs if needed */ if (p == 0) { - char tmp[50]; - if (jobid == -1) - sprintf(tmp, "The last job cannot be waited.\n"); - else - sprintf(tmp, "The job %i cannot be waited.\n", jobid); - send_list_line(s, tmp); - return; + p = first_finished_job; + while (p != 0 && p->jobid != jobid) + p = p->next; } + } - if (p->state == FINISHED || p->state == SKIPPED) { - send_waitjob_ok(s, p->result.errorlevel); - } else - add_to_notify_list(s, p->jobid); + if (p == 0) { + char tmp[50]; + if (jobid == -1) + sprintf(tmp, "The last job cannot be waited.\n"); + else + sprintf(tmp, "The job %i cannot be waited.\n", jobid); + send_list_line(s, tmp); + return; + } + + if (p->state == FINISHED || p->state == SKIPPED) { + send_waitjob_ok(s, p->result.errorlevel); + } else + add_to_notify_list(s, p->jobid); } void s_wait_running_job(int s, int jobid) { - struct Job *p = 0; - - /* The job finding algorithm should be similar to that of - * s_send_output, because this will be used by "-t" and "-c" */ - if (jobid == -1) { - /* This means that we want the output info of the running task, or that - * of the last job run */ - if (busy_slots > 0) { - p = firstjob; - if (p == 0) - error("Internal state WAITING, but job not run." - "firstjob = %x", firstjob); - } else { - p = first_finished_job; - if (p == 0) { - send_list_line(s, "No jobs.\n"); - return; - } - while (p->next != 0) - p = p->next; - } + struct Job *p = 0; + + /* The job finding algorithm should be similar to that of + * s_send_output, because this will be used by "-t" and "-c" */ + if (jobid == -1) { + /* This means that we want the output info of the running task, or that + * of the last job run */ + if (busy_slots > 0) { + p = firstjob; + if (p == 0) + error("Internal state WAITING, but job not run." + "firstjob = %x", + firstjob); } else { - p = firstjob; - while (p != 0 && p->jobid != jobid) - p = p->next; - - /* Look in finished jobs if needed */ - if (p == 0) { - p = first_finished_job; - while (p != 0 && p->jobid != jobid) - p = p->next; - } + p = first_finished_job; + if (p == 0) { + send_list_line(s, "No jobs.\n"); + return; + } + while (p->next != 0) + p = p->next; } + } else { + p = firstjob; + while (p != 0 && p->jobid != jobid) + p = p->next; + /* Look in finished jobs if needed */ if (p == 0) { - char tmp[50]; - if (jobid == -1) - sprintf(tmp, "The last job cannot be waited.\n"); - else - sprintf(tmp, "The job %i cannot be waited.\n", jobid); - send_list_line(s, tmp); - return; + p = first_finished_job; + while (p != 0 && p->jobid != jobid) + p = p->next; } + } - if (p->state == FINISHED || p->state == SKIPPED) { - send_waitjob_ok(s, p->result.errorlevel); - } else - add_to_notify_list(s, p->jobid); + if (p == 0) { + char tmp[50]; + if (jobid == -1) + sprintf(tmp, "The last job cannot be waited.\n"); + else + sprintf(tmp, "The job %i cannot be waited.\n", jobid); + send_list_line(s, tmp); + return; + } + + if (p->state == FINISHED || p->state == SKIPPED) { + send_waitjob_ok(s, p->result.errorlevel); + } else + add_to_notify_list(s, p->jobid); } void s_set_max_slots(int new_max_slots) { - if (new_max_slots > 0) - max_slots = new_max_slots; - else - warning("Received new_max_slots=%i", new_max_slots); + if (new_max_slots > 0) + max_slots = new_max_slots; + else + warning("Received new_max_slots=%i", new_max_slots); } void s_get_max_slots(int s) { - struct Msg m = default_msg(); + struct Msg m = default_msg(); - /* Message */ - m.type = GET_MAX_SLOTS_OK; - m.u.max_slots = max_slots; + /* Message */ + m.type = GET_MAX_SLOTS_OK; + m.u.max_slots = max_slots; - send_msg(s, &m); + send_msg(s, &m); } void s_move_urgent(int s, int jobid) { - struct Job *p = 0; - struct Job *tmp1; + struct Job *p = 0; + struct Job *tmp1; - if (jobid == -1) { - /* Find the last job added */ - p = firstjob; - - if (p != 0) - while (p->next != 0) - p = p->next; - } else { - p = firstjob; - while (p != 0 && p->jobid != jobid) - p = p->next; - } + if (jobid == -1) { + /* Find the last job added */ + p = firstjob; - if (p == 0 || firstjob->next == 0) { - char tmp[50]; - if (jobid == -1) - sprintf(tmp, "The last job cannot be urged.\n"); - else - sprintf(tmp, "The job %i cannot be urged.\n", jobid); - send_list_line(s, tmp); - return; - } + if (p != 0) + while (p->next != 0) + p = p->next; + } else { + p = firstjob; + while (p != 0 && p->jobid != jobid) + p = p->next; + } + + if (p == 0 || firstjob->next == 0) { + char tmp[50]; + if (jobid == -1) + sprintf(tmp, "The last job cannot be urged.\n"); + else + sprintf(tmp, "The job %i cannot be urged.\n", jobid); + send_list_line(s, tmp); + return; + } - /* Interchange the pointers */ - tmp1 = find_previous_job(p); - tmp1->next = p->next; - p->next = firstjob->next; - firstjob->next = p; - send_urgent_ok(s); + /* Interchange the pointers */ + tmp1 = find_previous_job(p); + tmp1->next = p->next; + p->next = firstjob->next; + firstjob->next = p; + send_urgent_ok(s); } void s_swap_jobs(int s, int jobid1, int jobid2) { - struct Job *p1, *p2; - struct Job *prev1, *prev2; - struct Job *tmp; + struct Job *p1, *p2; + struct Job *prev1, *prev2; + struct Job *tmp; - p1 = findjob(jobid1); - p2 = findjob(jobid2); + p1 = findjob(jobid1); + p2 = findjob(jobid2); - if (p1 == 0 || p2 == 0 || p1 == firstjob || p2 == firstjob) { - char prev[60]; - sprintf(prev, "The jobs %i and %i cannot be swapped.\n", jobid1, jobid2); - send_list_line(s, prev); - return; - } + if (p1 == 0 || p2 == 0 || p1 == firstjob || p2 == firstjob) { + char prev[60]; + sprintf(prev, "The jobs %i and %i cannot be swapped.\n", jobid1, jobid2); + send_list_line(s, prev); + return; + } - /* Interchange the pointers */ - prev1 = find_previous_job(p1); - prev2 = find_previous_job(p2); - prev1->next = p2; - prev2->next = p1; - tmp = p1->next; - p1->next = p2->next; - p2->next = tmp; + /* Interchange the pointers */ + prev1 = find_previous_job(p1); + prev2 = find_previous_job(p2); + prev1->next = p2; + prev2->next = p1; + tmp = p1->next; + p1->next = p2->next; + p2->next = tmp; - send_swap_jobs_ok(s); + send_swap_jobs_ok(s); } static void send_state(int s, enum Jobstate state) { - struct Msg m = default_msg(); + struct Msg m = default_msg(); - m.type = ANSWER_STATE; - m.u.state = state; + m.type = ANSWER_STATE; + m.u.state = state; - send_msg(s, &m); + send_msg(s, &m); } void s_send_state(int s, int jobid) { - struct Job *p = 0; - - if (jobid == -1) { - /* Find the last job added */ - p = firstjob; - - if (p != 0) - while (p->next != 0) - p = p->next; - - /* Look in finished jobs if needed */ - if (p == 0) { - p = first_finished_job; - if (p != 0) - while (p->next != 0) - p = p->next; - } + struct Job *p = 0; - } else { - p = get_job(jobid); - } + if (jobid == -1) { + /* Find the last job added */ + p = firstjob; + if (p != 0) + while (p->next != 0) + p = p->next; + + /* Look in finished jobs if needed */ if (p == 0) { - char tmp[50]; - if (jobid == -1) - sprintf(tmp, "The last job cannot be stated.\n"); - else - sprintf(tmp, "The job %i cannot be stated.\n", jobid); - send_list_line(s, tmp); - return; + p = first_finished_job; + if (p != 0) + while (p->next != 0) + p = p->next; } - /* Interchange the pointers */ - send_state(s, p->state); + } else { + p = get_job(jobid); + } + + if (p == 0) { + char tmp[50]; + if (jobid == -1) + sprintf(tmp, "The last job cannot be stated.\n"); + else + sprintf(tmp, "The job %i cannot be stated.\n", jobid); + send_list_line(s, tmp); + return; + } + + /* Interchange the pointers */ + send_state(s, p->state); } static void dump_job_struct(FILE *out, const struct Job *p) { - fprintf(out, " new_job\n"); - fprintf(out, " jobid %i\n", p->jobid); - fprintf(out, " command \"%s\"\n", p->command); - fprintf(out, " state %s\n", - jstate2string(p->state)); - fprintf(out, " result.errorlevel %i\n", p->result.errorlevel); - fprintf(out, " output_filename \"%s\"\n", - p->output_filename ? p->output_filename : "NULL"); - fprintf(out, " store_output %i\n", p->store_output); - fprintf(out, " pid %i\n", p->pid); - fprintf(out, " should_keep_finished %i\n", p->should_keep_finished); + fprintf(out, " new_job\n"); + fprintf(out, " jobid %i\n", p->jobid); + fprintf(out, " command \"%s\"\n", p->command); + fprintf(out, " state %s\n", jstate2string(p->state)); + fprintf(out, " result.errorlevel %i\n", p->result.errorlevel); + fprintf(out, " output_filename \"%s\"\n", + p->output_filename ? p->output_filename : "NULL"); + fprintf(out, " store_output %i\n", p->store_output); + fprintf(out, " pid %i\n", p->pid); + fprintf(out, " should_keep_finished %i\n", p->should_keep_finished); } void dump_jobs_struct(FILE *out) { - const struct Job *p; + const struct Job *p; - fprintf(out, "New_jobs\n"); + fprintf(out, "New_jobs\n"); - p = firstjob; - while (p != 0) { - dump_job_struct(out, p); - p = p->next; - } + p = firstjob; + while (p != 0) { + dump_job_struct(out, p); + p = p->next; + } - p = first_finished_job; - while (p != 0) { - dump_job_struct(out, p); - p = p->next; - } + p = first_finished_job; + while (p != 0) { + dump_job_struct(out, p); + p = p->next; + } } static void dump_notify_struct(FILE *out, const struct Notify *n) { - fprintf(out, " notify\n"); - fprintf(out, " jobid %i\n", n->jobid); - fprintf(out, " socket \"%i\"\n", n->socket); + fprintf(out, " notify\n"); + fprintf(out, " jobid %i\n", n->jobid); + fprintf(out, " socket \"%i\"\n", n->socket); } void dump_notifies_struct(FILE *out) { - const struct Notify *n; + const struct Notify *n; - fprintf(out, "New_notifies\n"); + fprintf(out, "New_notifies\n"); - n = first_notify; - while (n != 0) { - dump_notify_struct(out, n); - n = n->next; - } + n = first_notify; + while (n != 0) { + dump_notify_struct(out, n); + n = n->next; + } } void joblist_dump(int fd) { - struct Job *p; - char *buffer; - - buffer = joblistdump_headers(); - write(fd, buffer, strlen(buffer)); - free(buffer); - - /* We reuse the headers from the list */ - buffer = joblist_headers(); + struct Job *p; + char *buffer; + + buffer = joblistdump_headers(); + write(fd, buffer, strlen(buffer)); + free(buffer); + + /* We reuse the headers from the list */ + buffer = joblist_headers(); + write(fd, "# ", 2); + write(fd, buffer, strlen(buffer)); + + /* Show Finished jobs */ + p = first_finished_job; + while (p != 0) { + buffer = joblist_line(p); write(fd, "# ", 2); write(fd, buffer, strlen(buffer)); + free(buffer); + p = p->next; + } - /* Show Finished jobs */ - p = first_finished_job; - while (p != 0) { - buffer = joblist_line(p); - write(fd, "# ", 2); - write(fd, buffer, strlen(buffer)); - free(buffer); - p = p->next; - } - - write(fd, "\n", 1); + write(fd, "\n", 1); - /* Show Queued or Running jobs */ - p = firstjob; - while (p != 0) { - buffer = joblistdump_torun(p); - write(fd, buffer, strlen(buffer)); - free(buffer); - p = p->next; - } + /* Show Queued or Running jobs */ + p = firstjob; + while (p != 0) { + buffer = joblistdump_torun(p); + write(fd, buffer, strlen(buffer)); + free(buffer); + p = p->next; + } } -void s_get_logdir(int s) { - send_list_line(s, logdir); -} +void s_get_logdir(int s) { send_list_line(s, logdir); } -void s_set_logdir(const char* path) { - logdir = realloc(logdir, strlen(path) + 1); - strcpy(logdir, path); +void s_set_logdir(const char *path) { + logdir = realloc(logdir, strlen(path) + 1); + strcpy(logdir, path); } void s_get_env(int s, int size) { - char *var = malloc(size); - int res = recv_bytes(s, var, size); - if (res != size) - error("Receiving environment variable name"); - - char *val = getenv(var); - struct Msg m = default_msg(); - m.type = LIST_LINE; - m.u.size = val ? strlen(val) + 1 : 0; - send_msg(s, &m); - if (val) - send_bytes(s, val, m.u.size); - - free(var); + char *var = malloc(size); + int res = recv_bytes(s, var, size); + if (res != size) + error("Receiving environment variable name"); + + char *val = getenv(var); + struct Msg m = default_msg(); + m.type = LIST_LINE; + m.u.size = val ? strlen(val) + 1 : 0; + send_msg(s, &m); + if (val) + send_bytes(s, val, m.u.size); + + free(var); } void s_set_env(int s, int size) { - char *var = malloc(size); - int res = recv_bytes(s, var, size); - if (res != size) - error("Receiving environment variable name"); - - /* get the var name */ - char *name = strtok(var, "="); - - /* get the var value */ - char *val = strtok(NULL, "="); - setenv(name, val, 1); - free(var); + char *var = malloc(size); + int res = recv_bytes(s, var, size); + if (res != size) + error("Receiving environment variable name"); + + /* get the var name */ + char *name = strtok(var, "="); + + /* get the var value */ + char *val = strtok(NULL, "="); + setenv(name, val, 1); + free(var); } void s_unset_env(int s, int size) { - char *var = malloc(size); - int res = recv_bytes(s, var, size); - if (res != size) - error("Receiving environment variable name"); + char *var = malloc(size); + int res = recv_bytes(s, var, size); + if (res != size) + error("Receiving environment variable name"); - unsetenv(var); - free(var); + unsetenv(var); + free(var); } diff --git a/list.c b/list.c index 063a3e8..1596a24 100644 --- a/list.c +++ b/list.c @@ -4,409 +4,364 @@ Please find the license in the provided COPYING file. */ + #include #include #include #include + #include "main.h" +#include "user.h" /* From jobs.c */ extern int busy_slots; extern int max_slots; static char *shorten(char *line, int len) { - char *newline = (char *) malloc((len + 1) * sizeof(char)); - if (strlen(line) <= len) - strcpy(newline, line); - else { - snprintf(newline, len - 4, "%s", line); - strcat(newline, "..."); - } - return newline; + char *newline = (char *)malloc((len + 1) * sizeof(char)); + if (strlen(line) <= len) + strcpy(newline, line); + else { + snprintf(newline, len - 4, "%s", line); + strcat(newline, "..."); + } + return newline; } char *joblistdump_headers() { - char *line; - - line = malloc(600); - snprintf(line, 600, "#!/bin/sh\n# - task spooler (ts) job dump\n" - "# This file has been created because a SIGTERM killed\n" - "# your queue server.\n" - "# The finished commands are listed first.\n" - "# The commands running or to be run are stored as you would\n" - "# probably run them. Take care - some quotes may have got" - " broken\n\n"); - - return line; + char *line; + + line = malloc(600); + snprintf(line, 600, + "#!/bin/sh\n# - task spooler (ts) job dump\n" + "# This file has been created because a SIGTERM killed\n" + "# your queue server.\n" + "# The finished commands are listed first.\n" + "# The commands running or to be run are stored as you would\n" + "# probably run them. Take care - some quotes may have got" + " broken\n\n"); + + return line; } char *joblist_headers() { - char *line; - - line = malloc(100); - snprintf(line, 100, "%-4s %-10s %-20s %-8s %-6s %s [run=%i/%i]\n", - "ID", - "State", - "Output", - "E-Level", - "Time", - "Command", - busy_slots, - max_slots); - - return line; -} + char *line; -static int max(int a, int b) { - return a > b ? a : b; -} + line = malloc(100); + snprintf(line, 100, "%-4s %-10s %-20s %-8s %-6s %s [run=%i/%i]\n", "ID", + "State", "Output", "E-Level", "Time", "Command", busy_slots, + max_slots); -static const char *ofilename_shown(const struct Job *p) { - const char *output_filename; - - if (p->state == SKIPPED) { - output_filename = "(no output)"; - } else if (p->store_output) { - if (p->state == QUEUED) { - output_filename = "(file)"; - } else { - if (p->output_filename == 0) - /* This may happen due to concurrency - * problems */ - output_filename = "(...)"; - else - output_filename = shorten(p->output_filename, 20); - } - } else - output_filename = "stdout"; - - return output_filename; + return line; } -static char *print_noresult(const struct Job *p) { - const char *jobstate; - const char *output_filename; - int maxlen; - char *line; - /* 20 chars should suffice for a string like "[int,int,..]&& " */ - char dependstr[20] = ""; - int cmd_len; - - jobstate = jstate2string(p->state); - output_filename = ofilename_shown(p); - - maxlen = 4 + 1 + 10 + 1 + 20 + 1 + 8 + 1 - + 25 + 1 + strlen(p->command) + 20; /* 20 is the margin for errors */ - - if (p->label) - maxlen += 3 + strlen(p->label); - if (p->depend_on_size) { - maxlen += sizeof(dependstr); - int pos = 0; - if (p->depend_on[0] == -1) - pos += snprintf(&dependstr[pos], sizeof(dependstr), "[ "); - else - pos += snprintf(&dependstr[pos], sizeof(dependstr), "[%i", p->depend_on[0]); - - for (int i = 1; i < p->depend_on_size; i++) { - if (p->depend_on[i] == -1) - pos += snprintf(&dependstr[pos], sizeof(dependstr), ", "); - else - pos += snprintf(&dependstr[pos], sizeof(dependstr), ",%i", p->depend_on[i]); - } - pos += snprintf(&dependstr[pos], sizeof(dependstr), "]&& "); - } +static int max(int a, int b) { return a > b ? a : b; } - line = (char *) malloc(maxlen); - if (line == NULL) - error("Malloc for %i failed.\n", maxlen); - - cmd_len = max((strlen(p->command) + (term_width - maxlen)), 20); - if (p->label) { - char *label = shorten(p->label, 20); - char *cmd = shorten(p->command, cmd_len); - snprintf(line, maxlen, "%-4i %-10s %-20s %-8s %6s %s[%s]%s\n", - p->jobid, - jobstate, - output_filename, - "", - "", - dependstr, - label, - cmd); - free(label); - free(cmd); - } - else { - char *cmd = shorten(p->command, cmd_len); - snprintf(line, maxlen, "%-4i %-10s %-20s %-8s %6s %s%s\n", - p->jobid, - jobstate, - output_filename, - "", - "", - dependstr, - cmd); - free(cmd); +static const char *ofilename_shown(const struct Job *p) { + const char *output_filename; + + if (p->state == SKIPPED) { + output_filename = "(no output)"; + } else if (p->store_output) { + if (p->state == QUEUED) { + output_filename = "(file)"; + } else { + if (p->output_filename == 0) + /* This may happen due to concurrency + * problems */ + output_filename = "(...)"; + else + output_filename = shorten(p->output_filename, 20); } + } else + output_filename = "stdout"; - return line; + return output_filename; } -static char *print_result(const struct Job *p) { - const char *jobstate; - int maxlen; - char *line; - const char *output_filename; - /* 20 chars should suffice for a string like "[int,int,..]&& " */ - char dependstr[20] = ""; - float real_ms = p->result.real_ms; - char *unit = time_rep(&real_ms); - int cmd_len; - - jobstate = jstate2string(p->state); - output_filename = ofilename_shown(p); - - maxlen = 4 + 1 + 10 + 1 + 20 + 1 + 8 + 1 - + 25 + 1 + strlen(p->command) + 20; /* 20 is the margin for errors */ - - if (p->label) - maxlen += 3 + strlen(p->label); - if (p->depend_on_size) { - maxlen += sizeof(dependstr); - int pos = 0; - if (p->depend_on[0] == -1) - pos += snprintf(&dependstr[pos], sizeof(dependstr), "[ "); - else - pos += snprintf(&dependstr[pos], sizeof(dependstr), "[%i", p->depend_on[0]); - - for (int i = 1; i < p->depend_on_size; i++) { - if (p->depend_on[i] == -1) - pos += snprintf(&dependstr[pos], sizeof(dependstr), ", "); - else - pos += snprintf(&dependstr[pos], sizeof(dependstr), ",%i", p->depend_on[i]); - } - pos += snprintf(&dependstr[pos], sizeof(dependstr), "]&& "); +static char *print_noresult(const struct Job *p) { + const char *jobstate; + const char *output_filename; + int maxlen; + char *line; + /* 20 chars should suffice for a string like "[int,int,..]&& " */ + char dependstr[20] = ""; + int cmd_len; + + jobstate = jstate2string(p->state); + output_filename = ofilename_shown(p); + + char *uname = user_name[p->user_id]; + maxlen = 4 + 1 + 10 + 1 + 20 + 1 + 8 + 1 + 25 + 1 + strlen(p->command) + 20 + + strlen(uname) + 2; /* 20 is the margin for errors */ + + if (p->label) + maxlen += 3 + strlen(p->label); + if (p->depend_on_size) { + maxlen += sizeof(dependstr); + int pos = 0; + if (p->depend_on[0] == -1) + pos += snprintf(&dependstr[pos], sizeof(dependstr), "[ "); + else + pos += + snprintf(&dependstr[pos], sizeof(dependstr), "[%i", p->depend_on[0]); + + for (int i = 1; i < p->depend_on_size; i++) { + if (p->depend_on[i] == -1) + pos += snprintf(&dependstr[pos], sizeof(dependstr), ", "); + else + pos += snprintf(&dependstr[pos], sizeof(dependstr), ",%i", + p->depend_on[i]); } + pos += snprintf(&dependstr[pos], sizeof(dependstr), "]&& "); + } + + line = (char *)malloc(maxlen); + if (line == NULL) + error("Malloc for %i failed.\n", maxlen); + + cmd_len = max((strlen(p->command) + (term_width - maxlen)), 20); + if (p->label) { + char *label = shorten(p->label, 20); + char *cmd = shorten(p->command, cmd_len); + snprintf(line, maxlen, "%-4i %-10s %-10s %-20s %-8s %6s %s[%s]%s\n", + p->jobid, jobstate, uname, output_filename, "", "", dependstr, + label, cmd); + free(label); + free(cmd); + } else { + char *cmd = shorten(p->command, cmd_len); + snprintf(line, maxlen, "%-4i %-10s %-10s %-20s %-8s %6s %s%s\n", p->jobid, + jobstate, uname, output_filename, "", "", dependstr, cmd); + free(cmd); + } + + return line; +} - line = (char *) malloc(maxlen); - if (line == NULL) - error("Malloc for %i failed.\n", maxlen); - - cmd_len = max((strlen(p->command) + (term_width - maxlen)), 20); - if (p->label) { - char *label = shorten(p->label, 20); - char *cmd = shorten(p->command, cmd_len); - snprintf(line, maxlen, "%-4i %-10s %-20s %-8i %5.2f%s %s[%s]%s\n", - p->jobid, - jobstate, - output_filename, - p->result.errorlevel, - real_ms, - unit, - dependstr, - label, - cmd); - free(label); - free(cmd); - } - else { - char *cmd = shorten(p->command, cmd_len); - snprintf(line, maxlen, "%-4i %-10s %-20s %-8i %5.2f%s %s%s\n", - p->jobid, - jobstate, - output_filename, - p->result.errorlevel, - real_ms, - unit, - dependstr, - cmd); - free(cmd); +static char *print_result(const struct Job *p) { + const char *jobstate; + int maxlen; + char *line; + const char *output_filename; + /* 20 chars should suffice for a string like "[int,int,..]&& " */ + char dependstr[20] = ""; + float real_ms = p->result.real_ms; + char *unit = time_rep(&real_ms); + int cmd_len; + + jobstate = jstate2string(p->state); + output_filename = ofilename_shown(p); + + char *uname = user_name[p->user_id]; + + maxlen = 4 + 1 + 10 + 1 + 20 + 1 + 8 + 1 + 25 + 1 + strlen(p->command) + 20 + + strlen(uname) + 2; /* 20 is the margin for errors */ + + if (p->label) + maxlen += 3 + strlen(p->label); + if (p->depend_on_size) { + maxlen += sizeof(dependstr); + int pos = 0; + if (p->depend_on[0] == -1) + pos += snprintf(&dependstr[pos], sizeof(dependstr), "[ "); + else + pos += + snprintf(&dependstr[pos], sizeof(dependstr), "[%i", p->depend_on[0]); + + for (int i = 1; i < p->depend_on_size; i++) { + if (p->depend_on[i] == -1) + pos += snprintf(&dependstr[pos], sizeof(dependstr), ", "); + else + pos += snprintf(&dependstr[pos], sizeof(dependstr), ",%i", + p->depend_on[i]); } - - return line; + pos += snprintf(&dependstr[pos], sizeof(dependstr), "]&& "); + } + + line = (char *)malloc(maxlen); + if (line == NULL) + error("Malloc for %i failed.\n", maxlen); + + cmd_len = max((strlen(p->command) + (term_width - maxlen)), 20); + if (p->label) { + char *label = shorten(p->label, 20); + char *cmd = shorten(p->command, cmd_len); + snprintf(line, maxlen, "%-4i %-10s %-10s %-20s %-8i %5.2f%s %s[%s]%s\n", + p->jobid, jobstate, uname, output_filename, p->result.errorlevel, + real_ms, unit, dependstr, label, cmd); + free(label); + free(cmd); + } else { + char *cmd = shorten(p->command, cmd_len); + snprintf(line, maxlen, "%-4i %-10s %-10s %-20s %-8i %5.2f%s %s%s\n", + p->jobid, jobstate, uname, output_filename, p->result.errorlevel, + real_ms, unit, dependstr, cmd); + free(cmd); + } + + return line; } static char *plainprint_noresult(const struct Job *p) { - const char *jobstate; - const char *output_filename; - int maxlen; - char *line; - /* 20 chars should suffice for a string like "[int,int,..]&& " */ - char dependstr[20] = ""; - - jobstate = jstate2string(p->state); - output_filename = ofilename_shown(p); - - maxlen = 4 + 1 + 10 + 1 + 20 + 1 + 8 + 1 - + 25 + 1 + strlen(p->command) + 20; /* 20 is the margin for errors */ - - if (p->label) - maxlen += 3 + strlen(p->label); - - if (p->depend_on_size) { - maxlen += sizeof(dependstr); - int pos = 0; - if (p->depend_on[0] == -1) - pos += snprintf(&dependstr[pos], sizeof(dependstr), "[ "); - else - pos += snprintf(&dependstr[pos], sizeof(dependstr), "[%i", p->depend_on[0]); - - for (int i = 1; i < p->depend_on_size; i++) { - if (p->depend_on[i] == -1) - pos += snprintf(&dependstr[pos], sizeof(dependstr), ", "); - else - pos += snprintf(&dependstr[pos], sizeof(dependstr), ",%i", p->depend_on[i]); - } - pos += snprintf(&dependstr[pos], sizeof(dependstr), "]&& "); - } - - line = (char *) malloc(maxlen); - if (line == NULL) - error("Malloc for %i failed.\n", maxlen); - - if (p->label) - snprintf(line, maxlen, "%i\t%s\t%s\t%s\t%s\t%s\t[%s]\t%s\n", - p->jobid, - jobstate, - output_filename, - "", - "", - dependstr, - p->label, - p->command); + const char *jobstate; + const char *output_filename; + int maxlen; + char *line; + /* 20 chars should suffice for a string like "[int,int,..]&& " */ + char dependstr[20] = ""; + + jobstate = jstate2string(p->state); + output_filename = ofilename_shown(p); + char *uname = user_name[p->user_id]; + maxlen = 4 + 1 + 10 + 1 + 20 + 1 + 8 + 1 + 25 + 1 + strlen(p->command) + 20 + + strlen(uname) + 2; /* 20 is the margin for errors */ + + if (p->label) + maxlen += 3 + strlen(p->label); + + if (p->depend_on_size) { + maxlen += sizeof(dependstr); + int pos = 0; + if (p->depend_on[0] == -1) + pos += snprintf(&dependstr[pos], sizeof(dependstr), "[ "); else - snprintf(line, maxlen, "%i\t%s\t%s\t%s\t%s\t%s\t\t%s\n", - p->jobid, - jobstate, - output_filename, - "", - "", - dependstr, - p->command); - - return line; + pos += + snprintf(&dependstr[pos], sizeof(dependstr), "[%i", p->depend_on[0]); + + for (int i = 1; i < p->depend_on_size; i++) { + if (p->depend_on[i] == -1) + pos += snprintf(&dependstr[pos], sizeof(dependstr), ", "); + else + pos += snprintf(&dependstr[pos], sizeof(dependstr), ",%i", + p->depend_on[i]); + } + pos += snprintf(&dependstr[pos], sizeof(dependstr), "]&& "); + } + + line = (char *)malloc(maxlen); + if (line == NULL) + error("Malloc for %i failed.\n", maxlen); + + if (p->label) + snprintf(line, maxlen, "%i\t%s\t%s\t%s\t%s\t%s\t[%s]\t%s\n", p->jobid, + jobstate, output_filename, "", "", dependstr, p->label, + p->command); + else + snprintf(line, maxlen, "%i\t%s\t%s\t%s\t%s\t%s\t\t%s\n", p->jobid, jobstate, + output_filename, "", "", dependstr, p->command); + + return line; } static char *plainprint_result(const struct Job *p) { - const char *jobstate; - int maxlen; - char *line; - const char *output_filename; - /* 20 chars should suffice for a string like "[int,int,..]&& " */ - char dependstr[20] = ""; - float real_ms = p->result.real_ms; - char *unit = time_rep(&real_ms); - - jobstate = jstate2string(p->state); - output_filename = ofilename_shown(p); - - maxlen = 4 + 1 + 10 + 1 + 20 + 1 + 8 + 1 - + 25 + 1 + strlen(p->command) + 20; /* 20 is the margin for errors */ - - if (p->label) - maxlen += 3 + strlen(p->label); - - if (p->depend_on_size) { - maxlen += sizeof(dependstr); - int pos = 0; - if (p->depend_on[0] == -1) - pos += snprintf(&dependstr[pos], sizeof(dependstr), "[ "); - else - pos += snprintf(&dependstr[pos], sizeof(dependstr), "[%i", p->depend_on[0]); - - for (int i = 1; i < p->depend_on_size; i++) { - if (p->depend_on[i] == -1) - pos += snprintf(&dependstr[pos], sizeof(dependstr), ", "); - else - pos += snprintf(&dependstr[pos], sizeof(dependstr), ",%i", p->depend_on[i]); - } - pos += snprintf(&dependstr[pos], sizeof(dependstr), "]&& "); - } - - line = (char *) malloc(maxlen); - if (line == NULL) - error("Malloc for %i failed.\n", maxlen); - - if (p->label) - snprintf(line, maxlen, "%i\t%s\t%s\t%i\t%.2f\t%s\t%s\t[%s]\t%s\n", - p->jobid, - jobstate, - output_filename, - p->result.errorlevel, - real_ms, - unit, - dependstr, - p->label, - p->command); + const char *jobstate; + int maxlen; + char *line; + const char *output_filename; + /* 20 chars should suffice for a string like "[int,int,..]&& " */ + char dependstr[20] = ""; + float real_ms = p->result.real_ms; + char *unit = time_rep(&real_ms); + + jobstate = jstate2string(p->state); + output_filename = ofilename_shown(p); + + maxlen = 4 + 1 + 10 + 1 + 20 + 1 + 8 + 1 + 25 + 1 + strlen(p->command) + + 20; /* 20 is the margin for errors */ + + if (p->label) + maxlen += 3 + strlen(p->label); + + if (p->depend_on_size) { + maxlen += sizeof(dependstr); + int pos = 0; + if (p->depend_on[0] == -1) + pos += snprintf(&dependstr[pos], sizeof(dependstr), "[ "); else - snprintf(line, maxlen, "%i\t%s\t%s\t%i\t%.2f\t%s\t%s\t\t%s\n", - p->jobid, - jobstate, - output_filename, - p->result.errorlevel, - real_ms, - unit, - dependstr, - p->command); - - return line; + pos += + snprintf(&dependstr[pos], sizeof(dependstr), "[%i", p->depend_on[0]); + + for (int i = 1; i < p->depend_on_size; i++) { + if (p->depend_on[i] == -1) + pos += snprintf(&dependstr[pos], sizeof(dependstr), ", "); + else + pos += snprintf(&dependstr[pos], sizeof(dependstr), ",%i", + p->depend_on[i]); + } + pos += snprintf(&dependstr[pos], sizeof(dependstr), "]&& "); + } + + line = (char *)malloc(maxlen); + if (line == NULL) + error("Malloc for %i failed.\n", maxlen); + + if (p->label) + snprintf(line, maxlen, "%i\t%s\t%s\t%i\t%.2f\t%s\t%s\t[%s]\t%s\n", p->jobid, + jobstate, output_filename, p->result.errorlevel, real_ms, unit, + dependstr, p->label, p->command); + else + snprintf(line, maxlen, "%i\t%s\t%s\t%i\t%.2f\t%s\t%s\t\t%s\n", p->jobid, + jobstate, output_filename, p->result.errorlevel, real_ms, unit, + dependstr, p->command); + + return line; } char *joblist_line(const struct Job *p) { - char *line; + char *line; - if (p->state == FINISHED) - line = print_result(p); - else - line = print_noresult(p); + if (p->state == FINISHED) + line = print_result(p); + else + line = print_noresult(p); - return line; + return line; } char *joblist_line_plain(const struct Job *p) { - char *line; + char *line; - if (p->state == FINISHED) - line = plainprint_result(p); - else - line = plainprint_noresult(p); + if (p->state == FINISHED) + line = plainprint_result(p); + else + line = plainprint_noresult(p); - return line; + return line; } char *joblistdump_torun(const struct Job *p) { - int maxlen; - char *line; + int maxlen; + char *line; - maxlen = 10 + strlen(p->command) + 20; /* 20 is the margin for errors */ + maxlen = 10 + strlen(p->command) + 20; /* 20 is the margin for errors */ - line = (char *) malloc(maxlen); - if (line == NULL) - error("Malloc for %i failed.\n", maxlen); + line = (char *)malloc(maxlen); + if (line == NULL) + error("Malloc for %i failed.\n", maxlen); - snprintf(line, maxlen, "ts %s\n", p->command); + snprintf(line, maxlen, "ts %s\n", p->command); - return line; + return line; } char *time_rep(float *t) { - float time_in_sec = *t; - char *unit = "s"; + float time_in_sec = *t; + char *unit = "s"; + if (time_in_sec > 60) { + time_in_sec /= 60; + unit = "m"; + if (time_in_sec > 60) { - time_in_sec /= 60; - unit = "m"; - - if (time_in_sec > 60) { - time_in_sec /= 60; - unit = "h"; - - if (time_in_sec > 24) { - time_in_sec /= 24; - unit = "d"; - } - } + time_in_sec /= 60; + unit = "h"; + + if (time_in_sec > 24) { + time_in_sec /= 24; + unit = "d"; + } } - *t = time_in_sec; - return unit; + } + *t = time_in_sec; + return unit; } diff --git a/main.c b/main.c index 6408b92..6bd21e8 100644 --- a/main.c +++ b/main.c @@ -11,14 +11,14 @@ #define TS_MAKE_STR(x) _TS_MAKE_STR(x) #define _TS_MAKE_STR(x) #x -#include -#include -#include -#include #include +#include #include -#include +#include +#include #include +#include +#include #include "main.h" @@ -37,661 +37,704 @@ static char version[1024]; static void init_version() { #ifdef TS_VERSION - char *ts_version = TS_MAKE_STR(TS_VERSION); - sprintf(version, "Task Spooler %s - a task queue system for the unix user.\n" - "Copyright (C) 2007-2020 Duc Nguyen - Lluis Batlle i Rossell", ts_version); + char *ts_version = TS_MAKE_STR(TS_VERSION); + sprintf(version, + "Task Spooler %s - a task queue system for the unix user.\n" + "Copyright (C) 2007-2020 Duc Nguyen - Lluis Batlle i Rossell", + ts_version); #else - sprintf(version, "Task Spooler %s - a task queue system for the unix user.\n" - "Copyright (C) 2007-2020 Duc Nguyen - Lluis Batlle i Rossell", TS_VERSION_FALLBACK); + sprintf(version, + "Task Spooler %s - a task queue system for the unix user.\n" + "Copyright (C) 2007-2020 Duc Nguyen - Lluis Batlle i Rossell", + TS_VERSION_FALLBACK); #endif } static void default_command_line() { - command_line.request = c_LIST; - command_line.plain_list = 0; - command_line.need_server = 0; - command_line.store_output = 1; - command_line.should_go_background = 1; - command_line.should_keep_finished = 1; - command_line.gzip = 0; - command_line.send_output_by_mail = 0; - command_line.label = 0; - command_line.depend_on_size = 0; - command_line.depend_on = NULL; /* -1 means depend on previous */ - command_line.max_slots = 1; - command_line.wait_enqueuing = 1; - command_line.stderr_apart = 0; - command_line.num_slots = 1; - command_line.require_elevel = 0; - command_line.logfile = 0; + command_line.request = c_LIST; + command_line.plain_list = 0; + command_line.need_server = 0; + command_line.store_output = 1; + command_line.should_go_background = 1; + command_line.should_keep_finished = 1; + command_line.gzip = 0; + command_line.send_output_by_mail = 0; + command_line.label = 0; + command_line.depend_on_size = 0; + command_line.depend_on = NULL; /* -1 means depend on previous */ + command_line.max_slots = 1; + command_line.wait_enqueuing = 1; + command_line.stderr_apart = 0; + command_line.num_slots = 1; + command_line.require_elevel = 0; + command_line.logfile = 0; } struct Msg default_msg() { - struct Msg m; - memset(&m, 0, sizeof(struct Msg)); - return m; + struct Msg m; + memset(&m, 0, sizeof(struct Msg)); + return m; } struct Result default_result() { - struct Result result; - memset(&result, 0, sizeof(struct Result)); - return result; + struct Result result; + memset(&result, 0, sizeof(struct Result)); + return result; } void get_command(int index, int argc, char **argv) { - command_line.command.array = &(argv[index]); - command_line.command.num = argc - index; + command_line.command.array = &(argv[index]); + command_line.command.num = argc - index; } static int get_two_jobs(const char *str, int *j1, int *j2) { - char tmp[50]; - char *tmp2; - - if (strlen(str) >= 50) - return 0; - strcpy(tmp, str); - - tmp2 = strchr(tmp, '-'); - if (tmp2 == NULL) - return 0; - - /* We change the '-' to '\0', so we have a delimiter, - * and we can access the two strings for the ids */ - *tmp2 = '\0'; - /* Skip the '\0', and point tmp2 to the second id */ - ++tmp2; - - *j1 = atoi(tmp); - *j2 = atoi(tmp2); - return 1; + char tmp[50]; + char *tmp2; + + if (strlen(str) >= 50) + return 0; + strcpy(tmp, str); + + tmp2 = strchr(tmp, '-'); + if (tmp2 == NULL) + return 0; + + /* We change the '-' to '\0', so we have a delimiter, + * and we can access the two strings for the ids */ + *tmp2 = '\0'; + /* Skip the '\0', and point tmp2 to the second id */ + ++tmp2; + + *j1 = atoi(tmp); + *j2 = atoi(tmp2); + return 1; } -int strtok_int(char* str, char* delim, int* ids) { - int count = 0; - char *ptr = strtok(str, delim); - while(ptr != NULL) { - ids[count++] = atoi(ptr); - ptr = strtok(NULL, delim); - } - return count; +int strtok_int(char *str, char *delim, int *ids) { + int count = 0; + char *ptr = strtok(str, delim); + while (ptr != NULL) { + ids[count++] = atoi(ptr); + ptr = strtok(NULL, delim); + } + return count; } static struct option longOptions[] = { - {"get_label", optional_argument, NULL, 'a'}, - {"count_running", no_argument, NULL, 'R'}, - {"last_queue_id", no_argument, NULL, 'q'}, - {"full_cmd", optional_argument, NULL, 'F'}, - {"plain", no_argument, NULL, 0}, - {"get_logdir", no_argument, NULL, 0}, - {"set_logdir", required_argument, NULL, 0}, - {"getenv", required_argument, NULL, 0}, - {"setenv", required_argument, NULL, 0}, - {"unsetenv", required_argument, NULL, 0}, - {NULL, 0, NULL, 0} -}; + {"get_label", optional_argument, NULL, 'a'}, + {"count_running", no_argument, NULL, 'R'}, + {"last_queue_id", no_argument, NULL, 'q'}, + {"full_cmd", optional_argument, NULL, 'F'}, + {"plain", no_argument, NULL, 0}, + {"get_logdir", no_argument, NULL, 0}, + {"set_logdir", required_argument, NULL, 0}, + {"getenv", required_argument, NULL, 0}, + {"setenv", required_argument, NULL, 0}, + {"unsetenv", required_argument, NULL, 0}, + {NULL, 0, NULL, 0}}; void parse_opts(int argc, char **argv) { - int c; - int res; - int optionIdx = 0; - - /* Parse options */ - while (1) { - c = getopt_long(argc, argv, ":RTVhKzClnfmBEr:a:F:t:c:o:p:w:k:u:s:U:qi:N:L:dS:D:W:O:", - longOptions, &optionIdx); - - if (c == -1) - break; - - switch (c) { - case 0: - if (strcmp(longOptions[optionIdx].name, "get_logdir") == 0) { - command_line.request = c_GET_LOGDIR; - } else if (strcmp(longOptions[optionIdx].name, "set_logdir") == 0) { - command_line.request = c_SET_LOGDIR; - command_line.label = optarg; /* reuse this variable */ - } else if (strcmp(longOptions[optionIdx].name, "getenv") == 0) { - command_line.request = c_GET_ENV; - command_line.label = optarg; /* reuse this var */ - } else if (strcmp(longOptions[optionIdx].name, "setenv") == 0) { - command_line.request = c_SET_ENV; - command_line.label = optarg; /* reuse this var */ - } else if (strcmp(longOptions[optionIdx].name, "unsetenv") == 0) { - command_line.request = c_UNSET_ENV; - command_line.label = optarg; /* reuse this var */ - } else if (strcmp(longOptions[optionIdx].name, "plain") == 0) { - command_line.request = c_LIST; - command_line.plain_list = 1; - } else - error("Wrong option %s.", longOptions[optionIdx].name); - break; - case 'K': - command_line.request = c_KILL_SERVER; - command_line.should_go_background = 0; - break; - case 'T': - command_line.request = c_KILL_ALL; - break; - case 'k': - command_line.request = c_KILL_JOB; - command_line.jobid = atoi(optarg); - break; - case 'l': - command_line.request = c_LIST; - break; - case 'h': - command_line.request = c_SHOW_HELP; - break; - case 'd': - command_line.depend_on = (int*) malloc(sizeof(int)); - command_line.depend_on_size = 1; - command_line.depend_on[0] = -1; - break; - case 'V': - command_line.request = c_SHOW_VERSION; - break; - case 'C': - command_line.request = c_CLEAR_FINISHED; - break; - case 'c': - command_line.request = c_CAT; - command_line.jobid = atoi(optarg); - break; - case 'o': - command_line.request = c_SHOW_OUTPUT_FILE; - command_line.jobid = atoi(optarg); - break; - case 'O': - command_line.logfile = optarg; - break; - case 'n': - command_line.store_output = 0; - break; - case 'L': - command_line.label = optarg; - break; - case 'z': - command_line.gzip = 1; - break; - case 'f': - command_line.should_go_background = 0; - break; - case 'm': - command_line.send_output_by_mail = 1; - break; - case 't': - command_line.request = c_TAIL; - command_line.jobid = atoi(optarg); - break; - case 'p': - command_line.request = c_SHOW_PID; - command_line.jobid = atoi(optarg); - break; - case 'i': - command_line.request = c_INFO; - command_line.jobid = atoi(optarg); - break; - case 'q': - command_line.request = c_LAST_ID; - break; - case 'a': - command_line.request = c_GET_LABEL; - command_line.jobid = atoi(optarg); - break; - case 'F': - command_line.request = c_SHOW_CMD; - command_line.jobid = atoi(optarg); - break; - case 'N': - command_line.num_slots = atoi(optarg); - if (command_line.num_slots < 0) - command_line.num_slots = 0; - break; - case 'r': - command_line.request = c_REMOVEJOB; - command_line.jobid = atoi(optarg); - break; - case 'w': - command_line.request = c_WAITJOB; - command_line.jobid = atoi(optarg); - break; - case 'u': - command_line.request = c_URGENT; - command_line.jobid = atoi(optarg); - break; - case 's': - command_line.request = c_GET_STATE; - command_line.jobid = atoi(optarg); - break; - case 'S': - command_line.request = c_SET_MAX_SLOTS; - command_line.max_slots = atoi(optarg); - if (command_line.max_slots < 1) { - fprintf(stderr, "You should set at minimum 1 slot.\n"); - exit(-1); - } - break; - case 'D': - command_line.depend_on = (int*) malloc(strlen(optarg) * sizeof(int)); - command_line.depend_on_size = strtok_int(optarg, ",", command_line.depend_on); - break; - case 'W': - command_line.depend_on = (int*) malloc(strlen(optarg) * sizeof(int)); - command_line.depend_on_size = strtok_int(optarg, ",", command_line.depend_on); - command_line.require_elevel = 1; - break; - case 'U': - command_line.request = c_SWAP_JOBS; - res = get_two_jobs(optarg, &command_line.jobid, - &command_line.jobid2); - if (!res) { - fprintf(stderr, "Wrong for -U.\n"); - exit(-1); - } - if (command_line.jobid == command_line.jobid2) { - fprintf(stderr, "Wrong for -U. " - "Use different ids.\n"); - exit(-1); - } - break; - case 'B': - /* I picked 'B' quite at random among the letters left */ - command_line.wait_enqueuing = 0; - break; - case 'E': - command_line.stderr_apart = 1; - break; - case 'R': - command_line.request = c_COUNT_RUNNING; - break; - case ':': - switch (optopt) { - case 't': - command_line.request = c_TAIL; - command_line.jobid = -1; /* This means the 'last' job */ - break; - case 'c': - command_line.request = c_CAT; - command_line.jobid = -1; /* This means the 'last' job */ - break; - case 'o': - command_line.request = c_SHOW_OUTPUT_FILE; - command_line.jobid = -1; /* This means the 'last' job */ - break; - case 'p': - command_line.request = c_SHOW_PID; - command_line.jobid = -1; /* This means the 'last' job */ - break; - case 'i': - command_line.request = c_INFO; - command_line.jobid = -1; /* This means the 'last' job */ - break; - case 'r': - command_line.request = c_REMOVEJOB; - command_line.jobid = -1; /* This means the 'last' - added job */ - break; - case 'w': - command_line.request = c_WAITJOB; - command_line.jobid = -1; /* This means the 'last' - added job */ - break; - case 'u': - command_line.request = c_URGENT; - command_line.jobid = -1; /* This means the 'last' - added job */ - break; - case 's': - command_line.request = c_GET_STATE; - command_line.jobid = -1; /* This means the 'last' - added job */ - break; - case 'k': - command_line.request = c_KILL_JOB; - command_line.jobid = -1; /* This means the 'last' job */ - break; - case 'S': - command_line.request = c_GET_MAX_SLOTS; - break; - case 'a': - command_line.request = c_GET_LABEL; - command_line.jobid = -1; - break; - case 'F': - command_line.request = c_SHOW_CMD; - command_line.jobid = -1; - break; - default: - fprintf(stderr, "Option %c missing argument.\n", - optopt); - exit(-1); - } - break; - case '?': - fprintf(stderr, "Wrong option %c.\n", optopt); - exit(-1); - } - } - - command_line.command.num = 0; - - /* if the request is still the default option... - * (the default values should be centralized) */ - if (optind < argc && command_line.request == c_LIST) { - command_line.request = c_QUEUE; - get_command(optind, argc, argv); - } - - if (command_line.request != c_SHOW_HELP && - command_line.request != c_SHOW_VERSION) - command_line.need_server = 1; - - if (!command_line.store_output && !command_line.should_go_background) - command_line.should_keep_finished = 0; - - if (command_line.send_output_by_mail && ((!command_line.store_output) || - command_line.gzip)) { - fprintf(stderr, - "For e-mail, you should store the output (not through gzip)\n"); + int c; + int res; + int optionIdx = 0; + + /* Parse options */ + while (1) { + c = getopt_long(argc, argv, + ":ARTVhKzClnfmBEr:a:F:t:c:o:p:w:k:u:s:U:qi:N:L:dS:D:W:O:", + longOptions, &optionIdx); + + if (c == -1) + break; + + switch (c) { + case 0: + if (strcmp(longOptions[optionIdx].name, "get_logdir") == 0) { + command_line.request = c_GET_LOGDIR; + } else if (strcmp(longOptions[optionIdx].name, "set_logdir") == 0) { + command_line.request = c_SET_LOGDIR; + command_line.label = optarg; /* reuse this variable */ + } else if (strcmp(longOptions[optionIdx].name, "getenv") == 0) { + command_line.request = c_GET_ENV; + command_line.label = optarg; /* reuse this var */ + } else if (strcmp(longOptions[optionIdx].name, "setenv") == 0) { + command_line.request = c_SET_ENV; + command_line.label = optarg; /* reuse this var */ + } else if (strcmp(longOptions[optionIdx].name, "unsetenv") == 0) { + command_line.request = c_UNSET_ENV; + command_line.label = optarg; /* reuse this var */ + } else if (strcmp(longOptions[optionIdx].name, "plain") == 0) { + command_line.request = c_LIST; + command_line.plain_list = 1; + } else + error("Wrong option %s.", longOptions[optionIdx].name); + break; + case 'K': + command_line.request = c_KILL_SERVER; + command_line.should_go_background = 0; + break; + case 'T': + command_line.request = c_KILL_ALL; + break; + case 'k': + command_line.request = c_KILL_JOB; + command_line.jobid = atoi(optarg); + break; + case 'l': + command_line.request = c_LIST; + break; + case 'A': + command_line.request = c_LIST_ALL; + break; + case 'h': + command_line.request = c_SHOW_HELP; + break; + case 'd': + command_line.depend_on = (int *)malloc(sizeof(int)); + command_line.depend_on_size = 1; + command_line.depend_on[0] = -1; + break; + case 'V': + command_line.request = c_SHOW_VERSION; + break; + case 'C': + command_line.request = c_CLEAR_FINISHED; + break; + case 'c': + command_line.request = c_CAT; + command_line.jobid = atoi(optarg); + break; + case 'o': + command_line.request = c_SHOW_OUTPUT_FILE; + command_line.jobid = atoi(optarg); + break; + case 'O': + command_line.logfile = optarg; + break; + case 'n': + command_line.store_output = 0; + break; + case 'L': + command_line.label = optarg; + break; + case 'z': + command_line.gzip = 1; + break; + case 'f': + command_line.should_go_background = 0; + break; + case 'm': + command_line.send_output_by_mail = 1; + break; + case 't': + command_line.request = c_TAIL; + command_line.jobid = atoi(optarg); + break; + case 'p': + command_line.request = c_SHOW_PID; + command_line.jobid = atoi(optarg); + break; + case 'i': + command_line.request = c_INFO; + command_line.jobid = atoi(optarg); + break; + case 'q': + command_line.request = c_LAST_ID; + break; + case 'a': + command_line.request = c_GET_LABEL; + command_line.jobid = atoi(optarg); + break; + case 'F': + command_line.request = c_SHOW_CMD; + command_line.jobid = atoi(optarg); + break; + case 'N': + command_line.num_slots = atoi(optarg); + if (command_line.num_slots < 0) + command_line.num_slots = 0; + break; + case 'r': + command_line.request = c_REMOVEJOB; + command_line.jobid = atoi(optarg); + break; + case 'w': + command_line.request = c_WAITJOB; + command_line.jobid = atoi(optarg); + break; + case 'u': + command_line.request = c_URGENT; + command_line.jobid = atoi(optarg); + break; + case 's': + command_line.request = c_GET_STATE; + command_line.jobid = atoi(optarg); + break; + case 'S': + command_line.request = c_SET_MAX_SLOTS; + command_line.max_slots = atoi(optarg); + if (command_line.max_slots < 1) { + fprintf(stderr, "You should set at minimum 1 slot.\n"); + exit(-1); + } + break; + case 'D': + command_line.depend_on = (int *)malloc(strlen(optarg) * sizeof(int)); + command_line.depend_on_size = + strtok_int(optarg, ",", command_line.depend_on); + break; + case 'W': + command_line.depend_on = (int *)malloc(strlen(optarg) * sizeof(int)); + command_line.depend_on_size = + strtok_int(optarg, ",", command_line.depend_on); + command_line.require_elevel = 1; + break; + case 'U': + command_line.request = c_SWAP_JOBS; + res = get_two_jobs(optarg, &command_line.jobid, &command_line.jobid2); + if (!res) { + fprintf(stderr, "Wrong for -U.\n"); + exit(-1); + } + if (command_line.jobid == command_line.jobid2) { + fprintf(stderr, "Wrong for -U. " + "Use different ids.\n"); exit(-1); + } + break; + case 'B': + /* I picked 'B' quite at random among the letters left */ + command_line.wait_enqueuing = 0; + break; + case 'E': + command_line.stderr_apart = 1; + break; + case 'R': + command_line.request = c_COUNT_RUNNING; + break; + case ':': + switch (optopt) { + case 't': + command_line.request = c_TAIL; + command_line.jobid = -1; /* This means the 'last' job */ + break; + case 'c': + command_line.request = c_CAT; + command_line.jobid = -1; /* This means the 'last' job */ + break; + case 'o': + command_line.request = c_SHOW_OUTPUT_FILE; + command_line.jobid = -1; /* This means the 'last' job */ + break; + case 'p': + command_line.request = c_SHOW_PID; + command_line.jobid = -1; /* This means the 'last' job */ + break; + case 'i': + command_line.request = c_INFO; + command_line.jobid = -1; /* This means the 'last' job */ + break; + case 'r': + command_line.request = c_REMOVEJOB; + command_line.jobid = -1; /* This means the 'last' + added job */ + break; + case 'w': + command_line.request = c_WAITJOB; + command_line.jobid = -1; /* This means the 'last' + added job */ + break; + case 'u': + command_line.request = c_URGENT; + command_line.jobid = -1; /* This means the 'last' + added job */ + break; + case 's': + command_line.request = c_GET_STATE; + command_line.jobid = -1; /* This means the 'last' + added job */ + break; + case 'k': + command_line.request = c_KILL_JOB; + command_line.jobid = -1; /* This means the 'last' job */ + break; + case 'S': + command_line.request = c_GET_MAX_SLOTS; + break; + case 'a': + command_line.request = c_GET_LABEL; + command_line.jobid = -1; + break; + case 'F': + command_line.request = c_SHOW_CMD; + command_line.jobid = -1; + break; + default: + fprintf(stderr, "Option %c missing argument.\n", optopt); + exit(-1); + } + break; + case '?': + fprintf(stderr, "Wrong option %c.\n", optopt); + exit(-1); } + } + + command_line.command.num = 0; + + /* if the request is still the default option... + * (the default values should be centralized) */ + if (optind < argc && command_line.request == c_LIST) { + command_line.request = c_QUEUE; + get_command(optind, argc, argv); + } + + if (command_line.request != c_SHOW_HELP && + command_line.request != c_SHOW_VERSION) + command_line.need_server = 1; + + if (!command_line.store_output && !command_line.should_go_background) + command_line.should_keep_finished = 0; + + if (command_line.send_output_by_mail && + ((!command_line.store_output) || command_line.gzip)) { + fprintf(stderr, + "For e-mail, you should store the output (not through gzip)\n"); + exit(-1); + } } static void fill_first_3_handles() { - int tmp_pipe1[2]; - int tmp_pipe2[2]; - /* This will fill handles 0 and 1 */ - pipe(tmp_pipe1); - /* This will fill handles 2 and 3 */ - pipe(tmp_pipe2); - - close(tmp_pipe2[1]); + int tmp_pipe1[2]; + int tmp_pipe2[2]; + /* This will fill handles 0 and 1 */ + pipe(tmp_pipe1); + /* This will fill handles 2 and 3 */ + pipe(tmp_pipe2); + + close(tmp_pipe2[1]); } static void go_background() { - int pid; - pid = fork(); - - switch (pid) { - case -1: - error("fork failed"); - break; - case 0: - close(0); - close(1); - close(2); - /* This is a weird thing. But we will later want to - * allocate special files to the 0, 1 or 2 fds. It's - * almost impossible, if other important things got - * allocated here. */ - fill_first_3_handles(); - setsid(); - break; - default: - exit(0); - } + int pid; + pid = fork(); + + switch (pid) { + case -1: + error("fork failed"); + break; + case 0: + close(0); + close(1); + close(2); + /* This is a weird thing. But we will later want to + * allocate special files to the 0, 1 or 2 fds. It's + * almost impossible, if other important things got + * allocated here. */ + fill_first_3_handles(); + setsid(); + break; + default: + exit(0); + } } static void print_help(const char *cmd) { - puts(version); - printf("usage: %s [action] [-ngfmdE] [-L ] [-D ] [cmd...]\n", cmd); - printf("Env vars:\n"); - printf(" TS_SOCKET the path to the unix socket used by the ts command.\n"); - printf(" TS_MAILTO where to mail the result (on -m). Local user by default.\n"); - printf(" TS_MAXFINISHED maximum finished jobs in the queue.\n"); - printf(" TS_MAXCONN maximum number of ts connections at once.\n"); - printf(" TS_ONFINISH binary called on job end (passes jobid, error, outfile, command).\n"); - printf(" TS_ENV command called on enqueue. Its output determines the job information.\n"); - printf(" TS_SAVELIST filename which will store the list, if the server dies.\n"); - printf(" TS_SLOTS amount of jobs which can run at once, read on server start.\n"); - printf(" TMPDIR directory where to place the output files and the default socket.\n"); - printf("Long option actions:\n"); - printf(" --getenv [var] get the value of the specified variable in server environment.\n"); - printf(" --setenv [var] set the specified flag to server environment.\n"); - printf(" --unsetenv [var] remove the specified flag from server environment.\n"); - printf(" --get_label || -a [id] show the job label. Of the last added, if not specified.\n"); - printf(" --full_cmd || -F [id] show full command. Of the last added, if not specified.\n"); - printf(" --count_running || -R return the number of running jobs\n"); - printf(" --last_queue_id || -q show the job ID of the last added.\n"); - printf(" --get_logdir get the path containing log files.\n"); - printf(" --set_logdir [path] set the path containing log files.\n"); - printf(" --plain list jobs in plain tab-separated texts.\n"); - printf("Actions:\n"); - printf(" -K kill the task spooler server\n"); - printf(" -C clear the list of finished jobs\n"); - printf(" -l show the job list (default action)\n"); - printf(" -S [num] get/set the number of max simultaneous jobs of the server.\n"); - printf(" -t [id] \"tail -n 10 -f\" the output of the job. Last run if not specified.\n"); - printf(" -c [id] like -t, but shows all the lines. Last run if not specified.\n"); - printf(" -p [id] show the PID of the job. Last run if not specified.\n"); - printf(" -o [id] show the output file. Of last job run, if not specified.\n"); - printf(" -i [id] show job information. Of last job run, if not specified.\n"); - printf(" -s [id] show the job state. Of the last added, if not specified.\n"); - printf(" -r [id] remove a job. The last added, if not specified.\n"); - printf(" -w [id] wait for a job. The last added, if not specified.\n"); - printf(" -k [id] send SIGTERM to the job process group. The last run, if not specified.\n"); - printf(" -T send SIGTERM to all running job groups.\n"); - printf(" -u [id] put that job first. The last added, if not specified.\n"); - printf(" -U swap two jobs in the queue.\n"); - printf(" -h show this help\n"); - printf(" -V show the program version\n"); - printf("Options adding jobs:\n"); - printf(" -B in case of full clients on the server, quit instead of waiting.\n"); - printf(" -n don't store the output of the command.\n"); - printf(" -E Keep stderr apart, in a name like the output file, but adding '.e'.\n"); - printf(" -O Set name of the log file (without any path).\n"); - printf(" -z gzip the stored output (if not -n).\n"); - printf(" -f don't fork into background.\n"); - printf(" -m send the output by e-mail (uses sendmail).\n"); - printf(" -d the job will be run after the last job ends.\n"); - printf(" -D the job will be run after the job of given IDs ends.\n"); - printf(" -W the job will be run after the job of given IDs ends well (exit code 0).\n"); - printf(" -L [label] name this task with a label, to be distinguished on listing.\n"); - printf(" -N [num] number of slots required by the job (1 default).\n"); + puts(version); + printf("usage: %s [action] [-ngfmdE] [-L ] [-D ] [cmd...]\n", cmd); + printf("Env vars:\n"); + printf(" TS_SOCKET the path to the unix socket used by the ts command.\n"); + printf(" TS_MAILTO where to mail the result (on -m). Local user by " + "default.\n"); + printf(" TS_MAXFINISHED maximum finished jobs in the queue.\n"); + printf(" TS_MAXCONN maximum number of ts connections at once.\n"); + printf(" TS_ONFINISH binary called on job end (passes jobid, error, " + "outfile, command).\n"); + printf(" TS_ENV command called on enqueue. Its output determines the job " + "information.\n"); + printf(" TS_SAVELIST filename which will store the list, if the server " + "dies.\n"); + printf(" TS_SLOTS amount of jobs which can run at once, read on server " + "start.\n"); + printf(" TMPDIR directory where to place the output files and the " + "default socket.\n"); + printf("Long option actions:\n"); + printf(" --getenv [var] get the value of the specified " + "variable in server environment.\n"); + printf(" --setenv [var] set the specified flag to server " + "environment.\n"); + printf(" --unsetenv [var] remove the specified flag from " + "server environment.\n"); + printf(" --get_label || -a [id] show the job label. Of the last " + "added, if not specified.\n"); + printf(" --full_cmd || -F [id] show full command. Of the last " + "added, if not specified.\n"); + printf( + " --count_running || -R return the number of running jobs\n"); + printf( + " --last_queue_id || -q show the job ID of the last added.\n"); + printf( + " --get_logdir get the path containing log files.\n"); + printf( + " --set_logdir [path] set the path containing log files.\n"); + printf(" --plain list jobs in plain tab-separated " + "texts.\n"); + printf("Actions:\n"); + printf(" -K kill the task spooler server\n"); + printf(" -C clear the list of finished jobs\n"); + printf(" -l show the job list (default action)\n"); + printf(" -S [num] get/set the number of max simultaneous jobs of the " + "server.\n"); + printf(" -t [id] \"tail -n 10 -f\" the output of the job. Last run if " + "not specified.\n"); + printf(" -c [id] like -t, but shows all the lines. Last run if not " + "specified.\n"); + printf( + " -p [id] show the PID of the job. Last run if not specified.\n"); + printf(" -o [id] show the output file. Of last job run, if not " + "specified.\n"); + printf(" -i [id] show job information. Of last job run, if not " + "specified.\n"); + printf(" -s [id] show the job state. Of the last added, if not " + "specified.\n"); + printf(" -r [id] remove a job. The last added, if not specified.\n"); + printf(" -w [id] wait for a job. The last added, if not specified.\n"); + printf(" -k [id] send SIGTERM to the job process group. The last run, " + "if not specified.\n"); + printf(" -T send SIGTERM to all running job groups.\n"); + printf( + " -u [id] put that job first. The last added, if not specified.\n"); + printf(" -U swap two jobs in the queue.\n"); + printf(" -h show this help\n"); + printf(" -V show the program version\n"); + printf("Options adding jobs:\n"); + printf(" -B in case of full clients on the server, quit instead " + "of waiting.\n"); + printf(" -n don't store the output of the command.\n"); + printf(" -E Keep stderr apart, in a name like the output file, " + "but adding '.e'.\n"); + printf(" -O Set name of the log file (without any path).\n"); + printf(" -z gzip the stored output (if not -n).\n"); + printf(" -f don't fork into background.\n"); + printf(" -m send the output by e-mail (uses sendmail).\n"); + printf(" -d the job will be run after the last job ends.\n"); + printf( + " -D the job will be run after the job of given IDs ends.\n"); + printf(" -W the job will be run after the job of given IDs ends " + "well (exit code 0).\n"); + printf(" -L [label] name this task with a label, to be distinguished on " + "listing.\n"); + printf(" -N [num] number of slots required by the job (1 default).\n"); } -static void print_version() { - puts(version); -} +static void print_version() { puts(version); } static void set_getopt_env() { - old_getopt_env = getenv("POSIXLY_CORRECT"); - putenv(getopt_env); + old_getopt_env = getenv("POSIXLY_CORRECT"); + putenv(getopt_env); } static void unset_getopt_env() { - if (old_getopt_env == NULL) { - /* Wipe the string from the environment */ - putenv("POSIXLY_CORRECT"); - } else - sprintf(getopt_env, "POSIXLY_CORRECT=%s", old_getopt_env); + if (old_getopt_env == NULL) { + /* Wipe the string from the environment */ + putenv("POSIXLY_CORRECT"); + } else + sprintf(getopt_env, "POSIXLY_CORRECT=%s", old_getopt_env); } static void get_terminal_width() { - struct winsize ws; - ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); - term_width = ws.ws_col; + struct winsize ws; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); + term_width = ws.ws_col; } int main(int argc, char **argv) { - int errorlevel = 0; - - init_version(); - get_terminal_width(); - process_type = CLIENT; - - set_getopt_env(); - /* This is needed in a gnu system, so getopt works well */ - default_command_line(); - parse_opts(argc, argv); - unset_getopt_env(); - - /* This will be inherited by the server, if it's run */ - ignore_sigpipe(); - - if (command_line.need_server) { - ensure_server_up(); - c_check_version(); + int errorlevel = 0; + client_uid = getuid(); + printf("client_uid = %u\n", client_uid); + + init_version(); + get_terminal_width(); + process_type = CLIENT; + + set_getopt_env(); + /* This is needed in a gnu system, so getopt works well */ + default_command_line(); + parse_opts(argc, argv); + unset_getopt_env(); + + /* This will be inherited by the server, if it's run */ + ignore_sigpipe(); + + if (command_line.need_server) { + ensure_server_up(); + c_check_version(); + } + + switch (command_line.request) { + case c_SHOW_VERSION: + print_version(); + break; + case c_SHOW_HELP: + print_help(argv[0]); + break; + case c_QUEUE: + if (command_line.command.num <= 0) + error("Tried to queue a void command. parameters: %i", + command_line.command.num); + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_new_job(); + command_line.jobid = c_wait_newjob_ok(); + if (command_line.store_output) { + printf("%i\n", command_line.jobid); + fflush(stdout); } - - switch (command_line.request) { - case c_SHOW_VERSION: - print_version(); - break; - case c_SHOW_HELP: - print_help(argv[0]); - break; - case c_QUEUE: - if (command_line.command.num <= 0) - error("Tried to queue a void command. parameters: %i", - command_line.command.num); - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_new_job(); - command_line.jobid = c_wait_newjob_ok(); - if (command_line.store_output) { - printf("%i\n", command_line.jobid); - fflush(stdout); - } - if (command_line.should_go_background) { - go_background(); - c_wait_server_commands(); - } else { - errorlevel = c_wait_server_commands(); - } - break; - case c_LIST: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_list_jobs(); - c_wait_server_lines(); - break; - case c_KILL_SERVER: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_shutdown_server(); - break; - case c_CLEAR_FINISHED: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_clear_finished(); - break; - case c_TAIL: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - errorlevel = c_tail(); - /* This will not return! */ - break; - case c_CAT: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - errorlevel = c_cat(); - /* This will not return! */ - break; - case c_SHOW_OUTPUT_FILE: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_show_output_file(); - break; - case c_SHOW_PID: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_show_pid(); - break; - case c_KILL_ALL: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_kill_all_jobs(); - break; - case c_KILL_JOB: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_kill_job(); - break; - case c_INFO: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_show_info(); - break; - case c_LAST_ID: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_show_last_id(); - break; - case c_GET_LABEL: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_show_label(); - break; - case c_SHOW_CMD: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_show_cmd(); - break; - case c_REMOVEJOB: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_remove_job(); - break; - case c_WAITJOB: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - errorlevel = c_wait_job(); - break; - case c_URGENT: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_move_urgent(); - break; - case c_SET_MAX_SLOTS: - c_send_max_slots(command_line.max_slots); - break; - case c_GET_MAX_SLOTS: - c_get_max_slots(); - break; - case c_SWAP_JOBS: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_swap_jobs(); - break; - case c_COUNT_RUNNING: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_get_count_running(); - break; - case c_GET_STATE: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - /* This will also print the state into stdout */ - c_get_state(); - break; - case c_GET_LOGDIR: - c_get_logdir(); - break; - case c_SET_LOGDIR: - c_set_logdir(); - break; - case c_GET_ENV: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_get_env(); - break; - case c_SET_ENV: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_set_env(); - break; - case c_UNSET_ENV: - if (!command_line.need_server) - error("The command %i needs the server", command_line.request); - c_unset_env(); - break; + if (command_line.should_go_background) { + go_background(); + c_wait_server_commands(); + } else { + errorlevel = c_wait_server_commands(); } - - if (command_line.need_server) { - close(server_socket); - } - free(command_line.depend_on); - - return errorlevel; + break; + case c_LIST_ALL: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_list_jobs_all(); + c_wait_server_lines(); + break; + case c_LIST: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_list_jobs(); + c_wait_server_lines(); + break; + case c_KILL_SERVER: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_shutdown_server(); + break; + case c_CLEAR_FINISHED: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_clear_finished(); + break; + case c_TAIL: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + errorlevel = c_tail(); + /* This will not return! */ + break; + case c_CAT: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + errorlevel = c_cat(); + /* This will not return! */ + break; + case c_SHOW_OUTPUT_FILE: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_show_output_file(); + break; + case c_SHOW_PID: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_show_pid(); + break; + case c_KILL_ALL: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_kill_all_jobs(); + break; + case c_KILL_JOB: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_kill_job(); + break; + case c_INFO: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_show_info(); + break; + case c_LAST_ID: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_show_last_id(); + break; + case c_GET_LABEL: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_show_label(); + break; + case c_SHOW_CMD: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_show_cmd(); + break; + case c_REMOVEJOB: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_remove_job(); + break; + case c_WAITJOB: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + errorlevel = c_wait_job(); + break; + case c_URGENT: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_move_urgent(); + break; + case c_SET_MAX_SLOTS: + c_send_max_slots(command_line.max_slots); + break; + case c_GET_MAX_SLOTS: + c_get_max_slots(); + break; + case c_SWAP_JOBS: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_swap_jobs(); + break; + case c_COUNT_RUNNING: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_get_count_running(); + break; + case c_GET_STATE: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + /* This will also print the state into stdout */ + c_get_state(); + break; + case c_GET_LOGDIR: + c_get_logdir(); + break; + case c_SET_LOGDIR: + c_set_logdir(); + break; + case c_GET_ENV: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_get_env(); + break; + case c_SET_ENV: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_set_env(); + break; + case c_UNSET_ENV: + if (!command_line.need_server) + error("The command %i needs the server", command_line.request); + c_unset_env(); + break; + } + + if (command_line.need_server) { + close(server_socket); + } + free(command_line.depend_on); + + return errorlevel; } diff --git a/main.h b/main.h index 78d6b6c..dfbd262 100644 --- a/main.h +++ b/main.h @@ -4,224 +4,213 @@ Please find the license in the provided COPYING file. */ -enum { - CMD_LEN = 500, - PROTOCOL_VERSION = 730 -}; +enum { CMD_LEN = 500, PROTOCOL_VERSION = 730 }; enum MsgTypes { - KILL_SERVER, - NEWJOB, - NEWJOB_OK, - RUNJOB, - RUNJOB_OK, - ENDJOB, - LIST, - LIST_LINE, - CLEAR_FINISHED, - ASK_OUTPUT, - ANSWER_OUTPUT, - REMOVEJOB, - REMOVEJOB_OK, - WAITJOB, - WAIT_RUNNING_JOB, - WAITJOB_OK, - URGENT, - URGENT_OK, - GET_STATE, - ANSWER_STATE, - SWAP_JOBS, - SWAP_JOBS_OK, - INFO, - INFO_DATA, - SET_MAX_SLOTS, - GET_MAX_SLOTS, - GET_MAX_SLOTS_OK, - GET_VERSION, - VERSION, - NEWJOB_NOK, - COUNT_RUNNING, - GET_LABEL, - LAST_ID, - KILL_ALL, - GET_CMD, - GET_LOGDIR, - SET_LOGDIR, - GET_ENV, - SET_ENV, - UNSET_ENV + KILL_SERVER, + NEWJOB, + NEWJOB_OK, + RUNJOB, + RUNJOB_OK, + ENDJOB, + LIST, + LIST_ALL, + LIST_LINE, + CLEAR_FINISHED, + ASK_OUTPUT, + ANSWER_OUTPUT, + REMOVEJOB, + REMOVEJOB_OK, + WAITJOB, + WAIT_RUNNING_JOB, + WAITJOB_OK, + URGENT, + URGENT_OK, + GET_STATE, + ANSWER_STATE, + SWAP_JOBS, + SWAP_JOBS_OK, + INFO, + INFO_DATA, + SET_MAX_SLOTS, + GET_MAX_SLOTS, + GET_MAX_SLOTS_OK, + GET_VERSION, + VERSION, + NEWJOB_NOK, + COUNT_RUNNING, + GET_LABEL, + LAST_ID, + KILL_ALL, + GET_CMD, + GET_LOGDIR, + SET_LOGDIR, + GET_ENV, + SET_ENV, + UNSET_ENV }; enum Request { - c_QUEUE, - c_TAIL, - c_KILL_SERVER, - c_LIST, - c_CLEAR_FINISHED, - c_SHOW_HELP, - c_SHOW_VERSION, - c_CAT, - c_SHOW_OUTPUT_FILE, - c_SHOW_PID, - c_REMOVEJOB, - c_WAITJOB, - c_URGENT, - c_GET_STATE, - c_SWAP_JOBS, - c_INFO, - c_SET_MAX_SLOTS, - c_GET_MAX_SLOTS, - c_KILL_JOB, - c_COUNT_RUNNING, - c_GET_LABEL, - c_LAST_ID, - c_KILL_ALL, - c_SHOW_CMD, - c_GET_LOGDIR, - c_SET_LOGDIR, - c_GET_ENV, - c_SET_ENV, - c_UNSET_ENV + c_QUEUE, + c_TAIL, + c_KILL_SERVER, + c_LIST, + c_LIST_ALL, + c_CLEAR_FINISHED, + c_SHOW_HELP, + c_SHOW_VERSION, + c_CAT, + c_SHOW_OUTPUT_FILE, + c_SHOW_PID, + c_REMOVEJOB, + c_WAITJOB, + c_URGENT, + c_GET_STATE, + c_SWAP_JOBS, + c_INFO, + c_SET_MAX_SLOTS, + c_GET_MAX_SLOTS, + c_KILL_JOB, + c_COUNT_RUNNING, + c_GET_LABEL, + c_LAST_ID, + c_KILL_ALL, + c_SHOW_CMD, + c_GET_LOGDIR, + c_SET_LOGDIR, + c_GET_ENV, + c_SET_ENV, + c_UNSET_ENV }; struct CommandLine { - enum Request request; - int plain_list; - int need_server; - int store_output; - int stderr_apart; - int should_go_background; - int should_keep_finished; - int send_output_by_mail; - int gzip; - int *depend_on; /* -1 means depend on previous */ - int depend_on_size; - int max_slots; /* How many jobs to run at once */ - int jobid; /* When queuing a job, main.c will fill it automatically from - the server answer to NEWJOB */ - int jobid2; - int wait_enqueuing; - struct { - char **array; - int num; - } command; - char *label; - int num_slots; /* Slots for the job to use. Default 1 */ - int require_elevel; /* whether requires error level of dependencies or not */ - char *logfile; + enum Request request; + int plain_list; + int need_server; + int store_output; + int stderr_apart; + int should_go_background; + int should_keep_finished; + int send_output_by_mail; + int gzip; + int *depend_on; /* -1 means depend on previous */ + int depend_on_size; + int max_slots; /* How many jobs to run at once */ + int jobid; /* When queuing a job, main.c will fill it automatically from + the server answer to NEWJOB */ + int jobid2; + int wait_enqueuing; + struct { + char **array; + int num; + } command; + char *label; + int num_slots; /* Slots for the job to use. Default 1 */ + int require_elevel; /* whether requires error level of dependencies or not */ + char *logfile; }; -enum ProcessType { - CLIENT, - SERVER -}; +enum ProcessType { CLIENT, SERVER }; extern struct CommandLine command_line; extern enum ProcessType process_type; extern int server_socket; /* Used in the client */ -extern char* logdir; +extern char *logdir; extern int term_width; struct Msg; -enum Jobstate { - QUEUED, - RUNNING, - FINISHED, - SKIPPED, - HOLDING_CLIENT -}; +enum Jobstate { QUEUED, RUNNING, FINISHED, SKIPPED, HOLDING_CLIENT }; struct Msg { - enum MsgTypes type; - - union { - struct { - int command_size; - int store_output; - int should_keep_finished; - int label_size; - int env_size; - int depend_on_size; - int wait_enqueuing; - int num_slots; - } newjob; - struct { - int ofilename_size; - int store_output; - int pid; - } output; - int jobid; - struct Result { - int errorlevel; - int died_by_signal; - int signal; - float user_ms; - float system_ms; - float real_ms; - int skipped; - } result; - int size; - enum Jobstate state; - struct { - int jobid1; - int jobid2; - } swap; - int last_errorlevel; - int max_slots; - int version; - int count_running; - char *label; - struct { - int plain_list; - int term_width; - } list; - } u; + enum MsgTypes type; + int uid; + union { + struct { + int command_size; + int store_output; + int should_keep_finished; + int label_size; + int env_size; + int depend_on_size; + int wait_enqueuing; + int num_slots; + } newjob; + struct { + int ofilename_size; + int store_output; + int pid; + } output; + int jobid; + struct Result { + int errorlevel; + int died_by_signal; + int signal; + float user_ms; + float system_ms; + float real_ms; + int skipped; + } result; + int size; + enum Jobstate state; + struct { + int jobid1; + int jobid2; + } swap; + int last_errorlevel; + int max_slots; + int version; + int count_running; + char *label; + struct { + int plain_list; + int term_width; + } list; + } u; }; struct Procinfo { - char *ptr; - int nchars; - int allocchars; - struct timeval enqueue_time; - struct timeval start_time; - struct timeval end_time; + char *ptr; + int nchars; + int allocchars; + struct timeval enqueue_time; + struct timeval start_time; + struct timeval end_time; }; struct Job { - struct Job *next; - int jobid; - char *command; - enum Jobstate state; - struct Result result; /* Defined in msg.h */ - char *output_filename; - int store_output; - int pid; - int should_keep_finished; - int *depend_on; - int depend_on_size; - int *notify_errorlevel_to; - int notify_errorlevel_to_size; - int dependency_errorlevel; - char *label; - struct Procinfo info; - int num_slots; + struct Job *next; + int jobid; + char *command; + enum Jobstate state; + struct Result result; /* Defined in msg.h */ + char *output_filename; + int store_output; + int pid; + int user_id; + int should_keep_finished; + int *depend_on; + int depend_on_size; + int *notify_errorlevel_to; + int notify_errorlevel_to_size; + int dependency_errorlevel; + char *label; + struct Procinfo info; + int num_slots; }; enum ExitCodes { - EXITCODE_OK = 0, - EXITCODE_UNKNOWN_ERROR = -1, - EXITCODE_QUEUE_FULL = 2 + EXITCODE_OK = 0, + EXITCODE_UNKNOWN_ERROR = -1, + EXITCODE_QUEUE_FULL = 2 }; - - +int client_uid; /* main.c */ struct Msg default_msg(); struct Result default_result(); - /* client.c */ void c_new_job(); @@ -287,7 +276,7 @@ void c_get_logdir(); void c_set_logdir(); -char* get_logdir(); +char *get_logdir(); void c_get_env(); @@ -316,7 +305,7 @@ void s_process_runjob_ok(int jobid, char *oname, int pid); void s_send_output(int socket, int jobid); -int s_remove_job(int s, int *jobid); +int s_remove_job(int s, int *jobid, int client_uid); void s_remove_notification(int s); @@ -364,7 +353,7 @@ void s_kill_all_jobs(int s); void s_get_logdir(int s); -void s_set_logdir(const char*); +void s_set_logdir(const char *); void s_get_env(int s, int size); @@ -453,7 +442,7 @@ char *joblistdump_torun(const struct Job *p); char *joblistdump_headers(); -char *time_rep(float* t); +char *time_rep(float *t); /* print.c */ int fd_nprintf(int fd, int maxsize, const char *fmt, ...); @@ -485,3 +474,14 @@ char *get_environment(); /* tail.c */ int tail_file(const char *fname, int last_lines); + +/* user.c */ +void read_user_file(const char *path); +int get_user_id(int id); + +/* jobs.c */ +void s_user_status_all(int s); +void s_user_status(int s, int i); + +/* client.c */ +void c_list_jobs_all(); \ No newline at end of file diff --git a/server.c b/server.c index 625076d..d7cb4cf 100644 --- a/server.c +++ b/server.c @@ -4,46 +4,39 @@ Please find the license in the provided COPYING file. */ -#include -#include #include +#include +#include #ifdef linux #include #endif -#include -#include #include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include #include #include "main.h" -enum { - MAXCONN = 1000 -}; +enum { MAXCONN = 1000 }; -enum Break { - BREAK, - NOBREAK, - CLOSE -}; +enum Break { BREAK, NOBREAK, CLOSE }; -char* logdir; +char *logdir; /* Prototypes */ static void server_loop(int ls); -static enum Break -client_read(int index); +static enum Break client_read(int index); static void end_server(int ls); @@ -56,9 +49,9 @@ static void s_runjob(int jobid, int index); static void clean_after_client_disappeared(int socket, int index); struct Client_conn { - int socket; - int hasjob; - int jobid; + int socket; + int hasjob; + int jobid; }; /* Globals */ @@ -71,498 +64,504 @@ static int max_descriptors; extern int max_jobs; static void s_send_version(int s) { - struct Msg m = default_msg(); + struct Msg m = default_msg(); - m.type = VERSION; - m.u.version = PROTOCOL_VERSION; + m.type = VERSION; + m.u.version = PROTOCOL_VERSION; - send_msg(s, &m); + send_msg(s, &m); } static void sigterm_handler(int n) { - const char *dumpfilename; - int fd; - - /* Dump the job list if we should to */ - dumpfilename = getenv("TS_SAVELIST"); - if (dumpfilename != NULL) { - fd = open(dumpfilename, O_WRONLY | O_APPEND | O_CREAT, 0600); - if (fd != -1) { - joblist_dump(fd); - close(fd); - } else - warning("The TS_SAVELIST file \"%s\" cannot be opened", - dumpfilename); - } + const char *dumpfilename; + int fd; + + /* Dump the job list if we should to */ + dumpfilename = getenv("TS_SAVELIST"); + if (dumpfilename != NULL) { + fd = open(dumpfilename, O_WRONLY | O_APPEND | O_CREAT, 0600); + if (fd != -1) { + joblist_dump(fd); + close(fd); + } else + warning("The TS_SAVELIST file \"%s\" cannot be opened", dumpfilename); + } - /* path will be initialized for sure, before installing the handler */ - unlink(path); - exit(1); + /* path will be initialized for sure, before installing the handler */ + unlink(path); + exit(1); } static void set_default_maxslots() { - char *str; - - str = getenv("TS_SLOTS"); - if (str != NULL) { - int slots; - slots = abs(atoi(str)); - s_set_max_slots(slots); - } + char *str; + + str = getenv("TS_SLOTS"); + if (str != NULL) { + int slots; + slots = abs(atoi(str)); + s_set_max_slots(slots); + } } static void initialize_log_dir() { - char *tmpdir = getenv("TMPDIR") == NULL ? "/tmp" : getenv("TMPDIR"); - logdir = malloc(strlen(tmpdir) + 1); - strcpy(logdir, tmpdir); + char *tmpdir = getenv("TMPDIR") == NULL ? "/tmp" : getenv("TMPDIR"); + logdir = malloc(strlen(tmpdir) + 1); + strcpy(logdir, tmpdir); } static void install_sigterm_handler() { - struct sigaction act; + struct sigaction act; - act.sa_handler = sigterm_handler; - /* Reset the mask */ - memset(&act.sa_mask, 0, sizeof(act.sa_mask)); - act.sa_flags = 0; + act.sa_handler = sigterm_handler; + /* Reset the mask */ + memset(&act.sa_mask, 0, sizeof(act.sa_mask)); + act.sa_flags = 0; - sigaction(SIGTERM, &act, NULL); + sigaction(SIGTERM, &act, NULL); } static int get_max_descriptors() { - const int MARGIN = 5; /* stdin, stderr, listen socket, and whatever */ - int max; - struct rlimit rlim; - int res; - const char *str; - - max = MAXCONN; - - str = getenv("TS_MAXCONN"); - if (str != NULL) { - int user_maxconn; - user_maxconn = abs(atoi(str)); - if (max > user_maxconn) - max = user_maxconn; - } - - if (max > FD_SETSIZE) - max = FD_SETSIZE - MARGIN; - - /* I'd like to use OPEN_MAX or NR_OPEN, but I don't know if any - * of them is POSIX compliant */ - - res = getrlimit(RLIMIT_NOFILE, &rlim); - if (res != 0) - warning("getrlimit for open files"); - else { - if (max > rlim.rlim_cur) - max = rlim.rlim_cur - MARGIN; - } - - if (max < 1) - error("Too few opened descriptors available"); - - return max; + const int MARGIN = 5; /* stdin, stderr, listen socket, and whatever */ + int max; + struct rlimit rlim; + int res; + const char *str; + + max = MAXCONN; + + str = getenv("TS_MAXCONN"); + if (str != NULL) { + int user_maxconn; + user_maxconn = abs(atoi(str)); + if (max > user_maxconn) + max = user_maxconn; + } + + if (max > FD_SETSIZE) + max = FD_SETSIZE - MARGIN; + + /* I'd like to use OPEN_MAX or NR_OPEN, but I don't know if any + * of them is POSIX compliant */ + + res = getrlimit(RLIMIT_NOFILE, &rlim); + if (res != 0) + warning("getrlimit for open files"); + else { + if (max > rlim.rlim_cur) + max = rlim.rlim_cur - MARGIN; + } + + if (max < 1) + error("Too few opened descriptors available"); + + return max; } void server_main(int notify_fd, char *_path) { - int ls; - struct sockaddr_un addr; - int res; - char *dirpath; + int ls; + struct sockaddr_un addr; + int res; + char *dirpath; - process_type = SERVER; - max_descriptors = get_max_descriptors(); + process_type = SERVER; + max_descriptors = get_max_descriptors(); - /* Arbitrary limit, that will block the enqueuing, but should allow space - * for usual ts queries */ - max_jobs = max_descriptors - 5; + /* Arbitrary limit, that will block the enqueuing, but should allow space + * for usual ts queries */ + max_jobs = max_descriptors - 5; - path = _path; + path = _path; - /* Move the server to the socket directory */ - dirpath = malloc(strlen(path) + 1); - strcpy(dirpath, path); - chdir(dirname(dirpath)); - free(dirpath); + /* Move the server to the socket directory */ + dirpath = malloc(strlen(path) + 1); + strcpy(dirpath, path); + chdir(dirname(dirpath)); + free(dirpath); - nconnections = 0; + nconnections = 0; - ls = socket(AF_UNIX, SOCK_STREAM, 0); - if (ls == -1) - error("cannot create the listen socket in the server"); + ls = socket(AF_UNIX, SOCK_STREAM, 0); + if (ls == -1) + error("cannot create the listen socket in the server"); - addr.sun_family = AF_UNIX; - strcpy(addr.sun_path, path); + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, path); - res = bind(ls, (struct sockaddr *) &addr, sizeof(addr)); - if (res == -1) - error("Error binding."); + res = bind(ls, (struct sockaddr *)&addr, sizeof(addr)); + if (res == -1) + error("Error binding."); - res = listen(ls, 0); - if (res == -1) - error("Error listening."); + res = listen(ls, 0); + if (res == -1) + error("Error listening."); - install_sigterm_handler(); + install_sigterm_handler(); - set_default_maxslots(); + set_default_maxslots(); - initialize_log_dir(); + initialize_log_dir(); - notify_parent(notify_fd); + notify_parent(notify_fd); - server_loop(ls); + server_loop(ls); } static int get_conn_of_jobid(int jobid) { - int i; - for (i = 0; i < nconnections; ++i) - if (client_cs[i].hasjob && client_cs[i].jobid == jobid) - return i; - return -1; + int i; + for (i = 0; i < nconnections; ++i) + if (client_cs[i].hasjob && client_cs[i].jobid == jobid) + return i; + return -1; } static void server_loop(int ls) { - fd_set readset; - int i; - int maxfd; - int keep_loop = 1; - int newjob; - - while (keep_loop) { - FD_ZERO(&readset); - maxfd = 0; - /* If we can accept more connections, go on. - * Otherwise, the system block them (no accept will be done). */ - if (nconnections < max_descriptors) { - FD_SET(ls, &readset); - maxfd = ls; - } - for (i = 0; i < nconnections; ++i) { - FD_SET(client_cs[i].socket, &readset); - if (client_cs[i].socket > maxfd) - maxfd = client_cs[i].socket; - } - select(maxfd + 1, &readset, NULL, NULL, NULL); - if (FD_ISSET(ls, &readset)) { - int cs; - cs = accept(ls, NULL, NULL); - if (cs == -1) - error("Accepting from %i", ls); - client_cs[nconnections].hasjob = 0; - client_cs[nconnections].socket = cs; - ++nconnections; - } - for (i = 0; i < nconnections; ++i) - if (FD_ISSET(client_cs[i].socket, &readset)) { - enum Break b; - b = client_read(i); - /* Check if we should break */ - if (b == CLOSE) { - warning("Closing"); - /* On unknown message, we close the client, - or it may hang waiting for an answer */ - clean_after_client_disappeared(client_cs[i].socket, i); - } else if (b == BREAK) - keep_loop = 0; - } - /* This will return firstjob->jobid or -1 */ - newjob = next_run_job(); - if (newjob != -1) { - int conn, awaken_job; - conn = get_conn_of_jobid(newjob); - /* This next marks the firstjob state to RUNNING */ - s_mark_job_running(newjob); - s_runjob(newjob, conn); - - while ((awaken_job = wake_hold_client()) != -1) { - int wake_conn = get_conn_of_jobid(awaken_job); - if (wake_conn == -1) - error("The job awaken does not have a connection open"); - s_newjob_ok(wake_conn); - } - } + fd_set readset; + int i; + int maxfd; + int keep_loop = 1; + int newjob; + + while (keep_loop) { + FD_ZERO(&readset); + maxfd = 0; + /* If we can accept more connections, go on. + * Otherwise, the system block them (no accept will be done). */ + if (nconnections < max_descriptors) { + FD_SET(ls, &readset); + maxfd = ls; } + for (i = 0; i < nconnections; ++i) { + FD_SET(client_cs[i].socket, &readset); + if (client_cs[i].socket > maxfd) + maxfd = client_cs[i].socket; + } + select(maxfd + 1, &readset, NULL, NULL, NULL); + if (FD_ISSET(ls, &readset)) { + int cs; + cs = accept(ls, NULL, NULL); + if (cs == -1) + error("Accepting from %i", ls); + client_cs[nconnections].hasjob = 0; + client_cs[nconnections].socket = cs; + ++nconnections; + } + for (i = 0; i < nconnections; ++i) + if (FD_ISSET(client_cs[i].socket, &readset)) { + enum Break b; + b = client_read(i); + /* Check if we should break */ + if (b == CLOSE) { + warning("Closing"); + /* On unknown message, we close the client, + or it may hang waiting for an answer */ + clean_after_client_disappeared(client_cs[i].socket, i); + } else if (b == BREAK) + keep_loop = 0; + } + /* This will return firstjob->jobid or -1 */ + newjob = next_run_job(); + if (newjob != -1) { + int conn, awaken_job; + conn = get_conn_of_jobid(newjob); + /* This next marks the firstjob state to RUNNING */ + s_mark_job_running(newjob); + s_runjob(newjob, conn); + + while ((awaken_job = wake_hold_client()) != -1) { + int wake_conn = get_conn_of_jobid(awaken_job); + if (wake_conn == -1) + error("The job awaken does not have a connection open"); + s_newjob_ok(wake_conn); + } + } + } - end_server(ls); + end_server(ls); } static void end_server(int ls) { - close(ls); - unlink(path); - /* This comes from the parent, in the fork after server_main. - * This is the last use of path in this process.*/ - free(path); + close(ls); + unlink(path); + /* This comes from the parent, in the fork after server_main. + * This is the last use of path in this process.*/ + free(path); } static void remove_connection(int index) { - int i; + int i; - if (client_cs[index].hasjob) { - s_removejob(client_cs[index].jobid); - } + if (client_cs[index].hasjob) { + s_removejob(client_cs[index].jobid); + } - for (i = index; i < (nconnections - 1); ++i) { - memcpy(&client_cs[i], &client_cs[i + 1], sizeof(client_cs[0])); - } - nconnections--; + for (i = index; i < (nconnections - 1); ++i) { + memcpy(&client_cs[i], &client_cs[i + 1], sizeof(client_cs[0])); + } + nconnections--; } -static void -clean_after_client_disappeared(int socket, int index) { - /* Act as if the job ended. */ - int jobid = client_cs[index].jobid; - if (client_cs[index].hasjob) { - struct Result r = default_result(); - - r.errorlevel = -1; - r.died_by_signal = 1; - r.signal = SIGKILL; - r.user_ms = 0; - r.system_ms = 0; - r.real_ms = 0; - r.skipped = 0; - - warning("JobID %i quit while running.", jobid); - job_finished(&r, jobid); - /* For the dependencies */ - check_notify_list(jobid); - /* We don't want this connection to do anything - * more related to the jobid, secially on remove_connection - * when we receive the EOC. */ - client_cs[index].hasjob = 0; - } else - /* If it doesn't have a running job, - * it may well be a notification */ - s_remove_notification(socket); - - close(socket); - remove_connection(index); +static void clean_after_client_disappeared(int socket, int index) { + /* Act as if the job ended. */ + int jobid = client_cs[index].jobid; + if (client_cs[index].hasjob) { + struct Result r = default_result(); + + r.errorlevel = -1; + r.died_by_signal = 1; + r.signal = SIGKILL; + r.user_ms = 0; + r.system_ms = 0; + r.real_ms = 0; + r.skipped = 0; + + warning("JobID %i quit while running.", jobid); + job_finished(&r, jobid); + /* For the dependencies */ + check_notify_list(jobid); + /* We don't want this connection to do anything + * more related to the jobid, secially on remove_connection + * when we receive the EOC. */ + client_cs[index].hasjob = 0; + } else + /* If it doesn't have a running job, + * it may well be a notification */ + s_remove_notification(socket); + + close(socket); + remove_connection(index); } -static enum Break -client_read(int index) { - struct Msg m = default_msg(); - int s; - int res; - - s = client_cs[index].socket; - - /* Read the message */ - res = recv_msg(s, &m); - if (res == -1) { - warning("client recv failed"); - clean_after_client_disappeared(s, index); - return NOBREAK; - } else if (res == 0) { - clean_after_client_disappeared(s, index); - return NOBREAK; +static enum Break client_read(int index) { + struct Msg m = default_msg(); + int s; + int res; + + s = client_cs[index].socket; + + /* Read the message */ + res = recv_msg(s, &m); + if (res == -1) { + warning("client recv failed"); + clean_after_client_disappeared(s, index); + return NOBREAK; + } else if (res == 0) { + clean_after_client_disappeared(s, index); + return NOBREAK; + } + + /* Process message */ + switch (m.type) { + case KILL_SERVER: + return BREAK; /* break in the parent*/ + break; + case NEWJOB: + if (get_user_id(m.uid) == -1) { + break; } - - /* Process message */ - switch (m.type) { - case KILL_SERVER: - return BREAK; /* break in the parent*/ - break; - case NEWJOB: - client_cs[index].jobid = s_newjob(s, &m); - client_cs[index].hasjob = 1; - if (!job_is_holding_client(client_cs[index].jobid)) - s_newjob_ok(index); - else if (!m.u.newjob.wait_enqueuing) { - s_newjob_nok(index); - clean_after_client_disappeared(s, index); - } - break; - case RUNJOB_OK: { - char *buffer = 0; - if (m.u.output.store_output) { - /* Receive the output filename */ - buffer = (char *) malloc(m.u.output.ofilename_size); - res = recv_bytes(s, buffer, - m.u.output.ofilename_size); - if (res != m.u.output.ofilename_size) - error("Reading the ofilename"); - } - s_process_runjob_ok(client_cs[index].jobid, buffer, - m.u.output.pid); - } - break; - case KILL_ALL: - s_kill_all_jobs(s); - break; - case LIST: - term_width = m.u.list.term_width; - if (m.u.list.plain_list) - s_list_plain(s); - else - s_list(s); - /* We must actively close, meaning End of Lines */ - close(s); - remove_connection(index); - break; - case INFO: - s_job_info(s, m.u.jobid); - close(s); - remove_connection(index); - break; - case LAST_ID: - s_send_last_id(s); - break; - case GET_LABEL: - s_get_label(s, m.u.jobid); - break; - case GET_CMD: - s_send_cmd(s, m.u.jobid); - break; - case ENDJOB: - job_finished(&m.u.result, client_cs[index].jobid); - /* For the dependencies */ - check_notify_list(client_cs[index].jobid); - /* We don't want this connection to do anything - * more related to the jobid, secially on remove_connection - * when we receive the EOC. */ - client_cs[index].hasjob = 0; - break; - case CLEAR_FINISHED: - s_clear_finished(); - break; - case ASK_OUTPUT: - s_send_output(s, m.u.jobid); - break; - case REMOVEJOB: { - int went_ok; - /* Will update the jobid. If it's -1, will set the jobid found */ - went_ok = s_remove_job(s, &m.u.jobid); - if (went_ok) { - int i; - for (i = 0; i < nconnections; ++i) { - if (client_cs[i].hasjob && client_cs[i].jobid == m.u.jobid) { - close(client_cs[i].socket); - - /* So remove_connection doesn't call s_removejob again */ - client_cs[i].hasjob = 0; - - /* We don't try to remove any notification related to - * 'i', because it will be for sure a ts client for a job */ - remove_connection(i); - } - } - } - } - break; - case WAITJOB: - s_wait_job(s, m.u.jobid); - break; - case WAIT_RUNNING_JOB: - s_wait_running_job(s, m.u.jobid); - break; - case COUNT_RUNNING: - s_count_running_jobs(s); - break; - case URGENT: - s_move_urgent(s, m.u.jobid); - break; - case SET_MAX_SLOTS: - s_set_max_slots(m.u.max_slots); - break; - case GET_MAX_SLOTS: - s_get_max_slots(s); - break; - case SWAP_JOBS: - s_swap_jobs(s, m.u.swap.jobid1, - m.u.swap.jobid2); - break; - case GET_STATE: - s_send_state(s, m.u.jobid); - break; - case GET_ENV: - s_get_env(s, m.u.size); - break; - case SET_ENV: - s_set_env(s, m.u.size); - break; - case UNSET_ENV: - s_unset_env(s, m.u.size); - break; - case GET_VERSION: - s_send_version(s); - break; - case GET_LOGDIR: - s_get_logdir(s); - break; - case SET_LOGDIR: { - char *path = malloc(m.u.size); - recv_bytes(s, path, m.u.size); - s_set_logdir(path); + client_cs[index].jobid = s_newjob(s, &m); + client_cs[index].hasjob = 1; + if (!job_is_holding_client(client_cs[index].jobid)) + s_newjob_ok(index); + else if (!m.u.newjob.wait_enqueuing) { + s_newjob_nok(index); + clean_after_client_disappeared(s, index); + } + break; + case RUNJOB_OK: { + char *buffer = 0; + if (m.u.output.store_output) { + /* Receive the output filename */ + buffer = (char *)malloc(m.u.output.ofilename_size); + res = recv_bytes(s, buffer, m.u.output.ofilename_size); + if (res != m.u.output.ofilename_size) + error("Reading the ofilename"); + } + s_process_runjob_ok(client_cs[index].jobid, buffer, m.u.output.pid); + } break; + case KILL_ALL: + s_kill_all_jobs(s); + break; + case LIST: + term_width = m.u.list.term_width; + if (m.u.list.plain_list) + s_list_plain(s); + else + s_list(s); + /* We must actively close, meaning End of Lines */ + close(s); + remove_connection(index); + break; + case LIST_ALL: + term_width = m.u.list.term_width; + if (m.u.list.plain_list) + s_list_plain(s); + else + s_list(s); + s_user_status_all(s); + /* We must actively close, meaning End of Lines */ + close(s); + remove_connection(index); + break; + case INFO: + s_job_info(s, m.u.jobid); + close(s); + remove_connection(index); + break; + case LAST_ID: + s_send_last_id(s); + break; + case GET_LABEL: + s_get_label(s, m.u.jobid); + break; + case GET_CMD: + s_send_cmd(s, m.u.jobid); + break; + case ENDJOB: + job_finished(&m.u.result, client_cs[index].jobid); + /* For the dependencies */ + check_notify_list(client_cs[index].jobid); + /* We don't want this connection to do anything + * more related to the jobid, secially on remove_connection + * when we receive the EOC. */ + client_cs[index].hasjob = 0; + break; + case CLEAR_FINISHED: + s_clear_finished(); + break; + case ASK_OUTPUT: + s_send_output(s, m.u.jobid); + break; + case REMOVEJOB: { + int went_ok; + /* Will update the jobid. If it's -1, will set the jobid found */ + went_ok = s_remove_job(s, &m.u.jobid, m.uid); + if (went_ok) { + int i; + for (i = 0; i < nconnections; ++i) { + if (client_cs[i].hasjob && client_cs[i].jobid == m.u.jobid) { + close(client_cs[i].socket); + + /* So remove_connection doesn't call s_removejob again */ + client_cs[i].hasjob = 0; + + /* We don't try to remove any notification related to + * 'i', because it will be for sure a ts client for a job */ + remove_connection(i); } - close(s); - remove_connection(index); - break; - default: - /* Command not supported */ - /* On unknown message, we close the client, - or it may hang waiting for an answer */ - warning("Unknown message: %i", m.type); - return CLOSE; + } } - - return NOBREAK; /* normal */ + } break; + case WAITJOB: + s_wait_job(s, m.u.jobid); + break; + case WAIT_RUNNING_JOB: + s_wait_running_job(s, m.u.jobid); + break; + case COUNT_RUNNING: + s_count_running_jobs(s); + break; + case URGENT: + s_move_urgent(s, m.u.jobid); + break; + case SET_MAX_SLOTS: + s_set_max_slots(m.u.max_slots); + break; + case GET_MAX_SLOTS: + s_get_max_slots(s); + break; + case SWAP_JOBS: + s_swap_jobs(s, m.u.swap.jobid1, m.u.swap.jobid2); + break; + case GET_STATE: + s_send_state(s, m.u.jobid); + break; + case GET_ENV: + s_get_env(s, m.u.size); + break; + case SET_ENV: + s_set_env(s, m.u.size); + break; + case UNSET_ENV: + s_unset_env(s, m.u.size); + break; + case GET_VERSION: + s_send_version(s); + break; + case GET_LOGDIR: + s_get_logdir(s); + break; + case SET_LOGDIR: { + char *path = malloc(m.u.size); + recv_bytes(s, path, m.u.size); + s_set_logdir(path); + } + close(s); + remove_connection(index); + break; + default: + /* Command not supported */ + /* On unknown message, we close the client, + or it may hang waiting for an answer */ + warning("Unknown message: %i", m.type); + return CLOSE; + } + + return NOBREAK; /* normal */ } static void s_runjob(int jobid, int index) { - int s; + int s; - if (!client_cs[index].hasjob) - error("Run job of the client %i which doesn't have any job", index); + if (!client_cs[index].hasjob) + error("Run job of the client %i which doesn't have any job", index); - s = client_cs[index].socket; + s = client_cs[index].socket; - s_send_runjob(s, jobid); + s_send_runjob(s, jobid); } static void s_newjob_ok(int index) { - int s; - struct Msg m = default_msg(); + int s; + struct Msg m = default_msg(); - if (!client_cs[index].hasjob) - error("Run job of the client %i which doesn't have any job", index); + if (!client_cs[index].hasjob) + error("Run job of the client %i which doesn't have any job", index); - s = client_cs[index].socket; + s = client_cs[index].socket; - m.type = NEWJOB_OK; - m.u.jobid = client_cs[index].jobid; + m.type = NEWJOB_OK; + m.u.jobid = client_cs[index].jobid; - send_msg(s, &m); + send_msg(s, &m); } static void s_newjob_nok(int index) { - int s; - struct Msg m = default_msg(); + int s; + struct Msg m = default_msg(); - if (!client_cs[index].hasjob) - error("Run job of the client %i which doesn't have any job", index); + if (!client_cs[index].hasjob) + error("Run job of the client %i which doesn't have any job", index); - s = client_cs[index].socket; + s = client_cs[index].socket; - m.type = NEWJOB_NOK; + m.type = NEWJOB_NOK; - send_msg(s, &m); + send_msg(s, &m); } static void dump_conn_struct(FILE *out, const struct Client_conn *p) { - fprintf(out, " new_conn\n"); - fprintf(out, " socket %i\n", p->socket); - fprintf(out, " hasjob \"%i\"\n", p->hasjob); - fprintf(out, " jobid %i\n", p->jobid); + fprintf(out, " new_conn\n"); + fprintf(out, " socket %i\n", p->socket); + fprintf(out, " hasjob \"%i\"\n", p->hasjob); + fprintf(out, " jobid %i\n", p->jobid); } void dump_conns_struct(FILE *out) { - int i; + int i; - fprintf(out, "New_conns"); + fprintf(out, "New_conns"); - for (i = 0; i < nconnections; ++i) { - dump_conn_struct(out, &client_cs[i]); - } + for (i = 0; i < nconnections; ++i) { + dump_conn_struct(out, &client_cs[i]); + } } diff --git a/server_start.c b/server_start.c index 2b9920a..8705dd0 100644 --- a/server_start.c +++ b/server_start.c @@ -4,17 +4,17 @@ Please find the license in the provided COPYING file. */ -#include -#include -#include -#include -#include #include -#include +#include #include -#include +#include +#include +#include #include -#include +#include +#include +#include +#include #include "main.h" @@ -26,155 +26,154 @@ static int should_check_owner = 0; static int fork_server(); void create_socket_path(char **path) { - char *tmpdir; - char userid[20]; - int size; + char *tmpdir; + char userid[20]; + int size; - /* As a priority, TS_SOCKET mandates over the path creation */ - *path = getenv("TS_SOCKET"); - if (*path != 0) { - /* We need this in our memory, for forks and future 'free'. */ - size = strlen(*path) + 1; - *path = (char *) malloc(size); - strcpy(*path, getenv("TS_SOCKET")); + /* As a priority, TS_SOCKET mandates over the path creation */ + *path = getenv("TS_SOCKET"); + if (*path != 0) { + /* We need this in our memory, for forks and future 'free'. */ + size = strlen(*path) + 1; + *path = (char *)malloc(size); + strcpy(*path, getenv("TS_SOCKET")); - /* We don't want to check ownership of the socket here, - * as the user may have thought of some shared queue */ - should_check_owner = 0; - return; - } + /* We don't want to check ownership of the socket here, + * as the user may have thought of some shared queue */ + should_check_owner = 0; + return; + } - /* ... if the $TS_SOCKET doesn't exist ... */ + /* ... if the $TS_SOCKET doesn't exist ... */ - /* Create the path */ - tmpdir = getenv("TMPDIR"); - if (tmpdir == NULL) - tmpdir = "/tmp"; + /* Create the path */ + tmpdir = getenv("TMPDIR"); + if (tmpdir == NULL) + tmpdir = "/tmp"; - sprintf(userid, "%u", (unsigned int) getuid()); + sprintf(userid, "%u", (unsigned int)getuid()); - /* Calculate the size */ - size = strlen(tmpdir) + strlen("/socket-ts.") + strlen(userid) + 1; + /* Calculate the size */ + size = strlen(tmpdir) + strlen("/socket-ts.") + strlen(userid) + 1; - /* Freed after preparing the socket address */ - *path = (char *) malloc(size); + /* Freed after preparing the socket address */ + *path = (char *)malloc(size); - sprintf(*path, "%s/socket-ts.%s", tmpdir, userid); + sprintf(*path, "%s/socket-ts.%s", tmpdir, userid); - should_check_owner = 1; + should_check_owner = 1; } int try_connect(int s) { - struct sockaddr_un addr; - int res; + struct sockaddr_un addr; + int res; - addr.sun_family = AF_UNIX; - strcpy(addr.sun_path, socket_path); + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, socket_path); - res = connect(s, (struct sockaddr *) &addr, sizeof(addr)); + res = connect(s, (struct sockaddr *)&addr, sizeof(addr)); - return res; + return res; } -static void -try_check_ownership() { - int res; - struct stat socketstat; +static void try_check_ownership() { + int res; + struct stat socketstat; - if (!should_check_owner) - return; + if (!should_check_owner) + return; - res = stat(socket_path, &socketstat); + res = stat(socket_path, &socketstat); - if (res != 0) - error("Cannot state the socket %s.", socket_path); + if (res != 0) + error("Cannot state the socket %s.", socket_path); - if (socketstat.st_uid != getuid()) - error("The uid %i does not own the socket %s.", getuid(), socket_path); + if (socketstat.st_uid != getuid()) + error("The uid %i does not own the socket %s.", getuid(), socket_path); } void wait_server_up(int fd) { - char a; + char a; - read(fd, &a, 1); - close(fd); + read(fd, &a, 1); + close(fd); } /* Returns the fd where to wait for the parent notification */ static int fork_server() { - int pid; - int p[2]; - - /* !!! stdin/stdout */ - pipe(p); - - pid = fork(); - switch (pid) { - case 0: /* Child */ - close(p[0]); - close(server_socket); - /* Close all std handles for the server */ - close(0); - close(1); - close(2); - setsid(); - server_main(p[1], socket_path); - exit(0); - break; - case -1: /* Error */ - return -1; - default: /* Parent */ - close(p[1]); - } - /* Return the read fd */ - return p[0]; + int pid; + int p[2]; + + /* !!! stdin/stdout */ + pipe(p); + + pid = fork(); + switch (pid) { + case 0: /* Child */ + close(p[0]); + close(server_socket); + /* Close all std handles for the server */ + close(0); + close(1); + close(2); + setsid(); + server_main(p[1], socket_path); + exit(0); + break; + case -1: /* Error */ + return -1; + default: /* Parent */ + close(p[1]); + } + /* Return the read fd */ + return p[0]; } void notify_parent(int fd) { - char a = 'a'; - write(fd, &a, 1); - close(fd); + char a = 'a'; + write(fd, &a, 1); + close(fd); } int ensure_server_up() { - int res; - int notify_fd; + int res; + int notify_fd; + read_user_file("./user.txt"); + server_socket = socket(AF_UNIX, SOCK_STREAM, 0); + if (server_socket == -1) + error("getting the server socket"); - server_socket = socket(AF_UNIX, SOCK_STREAM, 0); - if (server_socket == -1) - error("getting the server socket"); + create_socket_path(&socket_path); - create_socket_path(&socket_path); + res = try_connect(server_socket); - res = try_connect(server_socket); + /* Good connection */ + if (res == 0) { + try_check_ownership(); + free(socket_path); + return 1; + } - /* Good connection */ - if (res == 0) { - try_check_ownership(); - free(socket_path); - return 1; - } + /* If error other than "No one listens on the other end"... */ + if (!(errno == ENOENT || errno == ECONNREFUSED)) + error("c: cannot connect to the server"); - /* If error other than "No one listens on the other end"... */ - if (!(errno == ENOENT || errno == ECONNREFUSED)) - error("c: cannot connect to the server"); + if (errno == ECONNREFUSED) + unlink(socket_path); - if (errno == ECONNREFUSED) - unlink(socket_path); + /* Try starting the server */ + notify_fd = fork_server(); + wait_server_up(notify_fd); + res = try_connect(server_socket); - /* Try starting the server */ - notify_fd = fork_server(); - wait_server_up(notify_fd); - res = try_connect(server_socket); + /* The second time didn't work. Abort. */ + if (res == -1) { + fprintf(stderr, "The server didn't come up.\n"); + exit(-1); + } - /* The second time didn't work. Abort. */ - if (res == -1) { - fprintf(stderr, "The server didn't come up.\n"); - exit(-1); - } + free(socket_path); - free(socket_path); - - /* Good connection on the second time */ - return 1; + /* Good connection on the second time */ + return 1; } diff --git a/user.c b/user.c new file mode 100644 index 0000000..e58c900 --- /dev/null +++ b/user.c @@ -0,0 +1,68 @@ +#include +#include +#include + +#include "user.h" + +void send_list_line(int s, const char *str); + +void read_user_file(const char *path) { + server_uid = getuid(); + user_number = 0; + FILE *fp; + fp = fopen(path, "r"); + if (fp == NULL) + exit(EXIT_FAILURE); + char *line = NULL; + size_t len = 0; + size_t read; + while ((read = getline(&line, &len, fp)) != -1) { + if (line[0] == '#') + continue; + + int res = sscanf(line, "%d %256s %d", &user_UID[user_number], + user_name[user_number], &user_max_slots[user_number]); + if (res != 3) + printf("error in read %s at line %s", path, line); + else { + // printf("%d %s %d\n", user_ID[user_number], user_name[user_number], + // user_slot[user_number]); + user_busy[user_number] = 0; + user_jobs[user_number] = 0; + user_queue[user_number] = 0; + user_number++; + } + } + + fclose(fp); + if (line) + free(line); +} + +void s_user_status_all(int s) { + char buffer[256]; + for (size_t i = 0; i < user_number; i++) { + snprintf(buffer, 256, "[%04d] %3d/%3d %20s %d\n", user_UID[i], user_busy[i], + user_max_slots[i], user_name[i], user_jobs[user_number]); + send_list_line(s, buffer); + } + snprintf(buffer, 256, "Service at UID:%d\n", server_uid); + send_list_line(s, buffer); +} + +void s_user_status(int s, int i) { + char buffer[256]; + + snprintf(buffer, 256, "[%04d] %3d/%3d %20s %d\n", user_UID[i], user_busy[i], + user_max_slots[i], user_name[i], user_jobs[user_number]); + send_list_line(s, buffer); +} + +int get_user_id(int uid) { + for (size_t i = 0; i < user_number; i++) { + if (uid == user_UID[i]) { + return i; + } + } + return -1; +} \ No newline at end of file diff --git a/user.h b/user.h new file mode 100644 index 0000000..bb83fb3 --- /dev/null +++ b/user.h @@ -0,0 +1,11 @@ +#define USER_NAME_WIDTH 256 +#define USER_MAX 50 + +char user_name[USER_MAX][USER_NAME_WIDTH]; +int server_uid; +int user_max_slots[USER_MAX]; +int user_UID[USER_MAX]; +int user_busy[USER_MAX]; +int user_jobs[USER_MAX]; +int user_queue[USER_MAX]; +int user_number; \ No newline at end of file diff --git a/user.txt b/user.txt new file mode 100644 index 0000000..d715ad0 --- /dev/null +++ b/user.txt @@ -0,0 +1,3 @@ +#uid user maxslot +1000 Kylin 3 +2 user 100 From 8db9a368a12627a389af9c16b78fb57bde16eb80 Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 2 Aug 2022 15:38:41 +0800 Subject: [PATCH 02/91] version 0.0.3 --- jobs.c | 23 ++++++++++++++++------- main.c | 1 + main.h | 2 +- server.c | 7 +++++-- user.c | 6 +++++- 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/jobs.c b/jobs.c index 0c4e30e..cb652bf 100644 --- a/jobs.c +++ b/jobs.c @@ -824,21 +824,30 @@ void job_finished(const struct Result *result, int jobid) { } } -void s_clear_finished() { - struct Job *p; - - if (first_finished_job == 0) +void s_clear_finished(int user_id) { + struct Job newjob; + newjob.next = NULL; + struct Job *p, *other_user_job = &newjob; + if (first_finished_job == NULL) return; p = first_finished_job; - first_finished_job = 0; + if (p->user_id == user_id) { + first_finished_job = NULL; + } - while (p != 0) { + while (p != NULL) { struct Job *tmp; tmp = p->next; - destroy_job(p); + if (p->user_id == user_id) { + destroy_job(p); + } else { + other_user_job->next = p; + other_user_job = p; + } p = tmp; } + first_finished_job = newjob.next; } void s_process_runjob_ok(int jobid, char *oname, int pid) { diff --git a/main.c b/main.c index 6bd21e8..d1fd8cf 100644 --- a/main.c +++ b/main.c @@ -73,6 +73,7 @@ static void default_command_line() { struct Msg default_msg() { struct Msg m; memset(&m, 0, sizeof(struct Msg)); + m.uid = getuid(); return m; } diff --git a/main.h b/main.h index dfbd262..1a2744f 100644 --- a/main.h +++ b/main.h @@ -299,7 +299,7 @@ int next_run_job(); void s_mark_job_running(int jobid); -void s_clear_finished(); +void s_clear_finished(int user_id); void s_process_runjob_ok(int jobid, char *oname, int pid); diff --git a/server.c b/server.c index d7cb4cf..ec6e68d 100644 --- a/server.c +++ b/server.c @@ -26,6 +26,7 @@ #include #include "main.h" +#include "user.h" enum { MAXCONN = 1000 }; @@ -348,6 +349,7 @@ static enum Break client_read(int index) { clean_after_client_disappeared(s, index); return NOBREAK; } + int user_id = get_user_id(m.uid); /* Process message */ switch (m.type) { @@ -355,7 +357,7 @@ static enum Break client_read(int index) { return BREAK; /* break in the parent*/ break; case NEWJOB: - if (get_user_id(m.uid) == -1) { + if (user_id == -1) { break; } client_cs[index].jobid = s_newjob(s, &m); @@ -426,7 +428,8 @@ static enum Break client_read(int index) { client_cs[index].hasjob = 0; break; case CLEAR_FINISHED: - s_clear_finished(); + if (user_id != -1) + s_clear_finished(user_id); break; case ASK_OUTPUT: s_send_output(s, m.u.jobid); diff --git a/user.c b/user.c index e58c900..1d35d4f 100644 --- a/user.c +++ b/user.c @@ -5,9 +5,13 @@ #include "user.h" void send_list_line(int s, const char *str); +void error(const char *str, ...); void read_user_file(const char *path) { server_uid = getuid(); + if (server_uid != 0) { + error("the service is not run as root!"); + } user_number = 0; FILE *fp; fp = fopen(path, "r"); @@ -59,7 +63,7 @@ void s_user_status(int s, int i) { } int get_user_id(int uid) { - for (size_t i = 0; i < user_number; i++) { + for (int i = 0; i < user_number; i++) { if (uid == user_UID[i]) { return i; } From 43a5f500363a90006316dd993cd3e53ee73c1fa4 Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 2 Aug 2022 16:07:51 +0800 Subject: [PATCH 03/91] version 0.0.4 --- server.c | 22 +++++++++++++++++++--- server_start.c | 10 ++++------ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/server.c b/server.c index ec6e68d..630dc25 100644 --- a/server.c +++ b/server.c @@ -17,14 +17,14 @@ #include #include #include +#include #include #include #include +#include #include #include -#include - #include "main.h" #include "user.h" @@ -104,6 +104,18 @@ static void set_default_maxslots() { } } +static const char *get_user_path() { + char *str; + str = getenv("TS_SLOTS"); + if (str != NULL) { + return str; + } else { + return "/home/kylin/task-spooler/user.txt"; + } +} + +static void set_socket_model(const char *path) { chmod(path, 0777); } + static void initialize_log_dir() { char *tmpdir = getenv("TMPDIR") == NULL ? "/tmp" : getenv("TMPDIR"); logdir = malloc(strlen(tmpdir) + 1); @@ -196,6 +208,9 @@ void server_main(int notify_fd, char *_path) { if (res == -1) error("Error listening."); + read_user_file(get_user_path()); + set_socket_model(_path); + install_sigterm_handler(); set_default_maxslots(); @@ -354,7 +369,8 @@ static enum Break client_read(int index) { /* Process message */ switch (m.type) { case KILL_SERVER: - return BREAK; /* break in the parent*/ + if (m.uid == getuid()) + return BREAK; /* break in the parent*/ break; case NEWJOB: if (user_id == -1) { diff --git a/server_start.c b/server_start.c index 8705dd0..1623db6 100644 --- a/server_start.c +++ b/server_start.c @@ -27,7 +27,7 @@ static int fork_server(); void create_socket_path(char **path) { char *tmpdir; - char userid[20]; + char userid[20] = "root"; int size; /* As a priority, TS_SOCKET mandates over the path creation */ @@ -51,7 +51,7 @@ void create_socket_path(char **path) { if (tmpdir == NULL) tmpdir = "/tmp"; - sprintf(userid, "%u", (unsigned int)getuid()); + // sprintf(userid, "%u", (unsigned int)getuid()); /* Calculate the size */ size = strlen(tmpdir) + strlen("/socket-ts.") + strlen(userid) + 1; @@ -88,8 +88,8 @@ static void try_check_ownership() { if (res != 0) error("Cannot state the socket %s.", socket_path); - if (socketstat.st_uid != getuid()) - error("The uid %i does not own the socket %s.", getuid(), socket_path); + // if (socketstat.st_uid != getuid()) + // error("The uid %i does not own the socket %s.", getuid(), socket_path); } void wait_server_up(int fd) { @@ -138,7 +138,6 @@ void notify_parent(int fd) { int ensure_server_up() { int res; int notify_fd; - read_user_file("./user.txt"); server_socket = socket(AF_UNIX, SOCK_STREAM, 0); if (server_socket == -1) error("getting the server socket"); @@ -171,7 +170,6 @@ int ensure_server_up() { fprintf(stderr, "The server didn't come up.\n"); exit(-1); } - free(socket_path); /* Good connection on the second time */ From 1b7fd8768d578a742053fda123d7196e02664e4d Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 2 Aug 2022 16:43:43 +0800 Subject: [PATCH 04/91] version 0.0.5 --- client.c | 8 ++++++++ main.c | 8 +++++++- main.h | 4 ++++ server.c | 23 ++++++++++++----------- user.c | 25 ++++++++++++++++++------- user.txt | 1 + 6 files changed, 50 insertions(+), 19 deletions(-) diff --git a/client.c b/client.c index 735f589..6dbef8a 100644 --- a/client.c +++ b/client.c @@ -269,6 +269,14 @@ void c_show_info() { } } +void c_refresh_user() { + struct Msg m = default_msg(); + // int res; + + m.type = REFRESH_USERS; + send_msg(server_socket, &m); +} + void c_show_last_id() { struct Msg m = default_msg(); int res; diff --git a/main.c b/main.c index d1fd8cf..b911913 100644 --- a/main.c +++ b/main.c @@ -142,7 +142,7 @@ void parse_opts(int argc, char **argv) { /* Parse options */ while (1) { c = getopt_long(argc, argv, - ":ARTVhKzClnfmBEr:a:F:t:c:o:p:w:k:u:s:U:qi:N:L:dS:D:W:O:", + ":AXRTVhKzClnfmBEr:a:F:t:c:o:p:w:k:u:s:U:qi:N:L:dS:D:W:O:", longOptions, &optionIdx); if (c == -1) @@ -177,6 +177,9 @@ void parse_opts(int argc, char **argv) { case 'T': command_line.request = c_KILL_ALL; break; + case 'X': + command_line.request = c_REFRESH_USER; + break; case 'k': command_line.request = c_KILL_JOB; command_line.jobid = atoi(optarg); @@ -573,6 +576,9 @@ int main(int argc, char **argv) { } switch (command_line.request) { + case c_REFRESH_USER: + c_refresh_user(); + break; case c_SHOW_VERSION: print_version(); break; diff --git a/main.h b/main.h index 1a2744f..71709f5 100644 --- a/main.h +++ b/main.h @@ -16,6 +16,7 @@ enum MsgTypes { LIST, LIST_ALL, LIST_LINE, + REFRESH_USERS, CLEAR_FINISHED, ASK_OUTPUT, ANSWER_OUTPUT, @@ -56,6 +57,7 @@ enum Request { c_KILL_SERVER, c_LIST, c_LIST_ALL, + c_REFRESH_USER, c_CLEAR_FINISHED, c_SHOW_HELP, c_SHOW_VERSION, @@ -478,6 +480,8 @@ int tail_file(const char *fname, int last_lines); /* user.c */ void read_user_file(const char *path); int get_user_id(int id); +void c_refresh_user(); +const char *get_user_path(); /* jobs.c */ void s_user_status_all(int s); diff --git a/server.c b/server.c index 630dc25..9a5676a 100644 --- a/server.c +++ b/server.c @@ -104,16 +104,6 @@ static void set_default_maxslots() { } } -static const char *get_user_path() { - char *str; - str = getenv("TS_SLOTS"); - if (str != NULL) { - return str; - } else { - return "/home/kylin/task-spooler/user.txt"; - } -} - static void set_socket_model(const char *path) { chmod(path, 0777); } static void initialize_log_dir() { @@ -208,6 +198,12 @@ void server_main(int notify_fd, char *_path) { if (res == -1) error("Error listening."); + for (int i = 0; i < USER_MAX; i++) { + user_busy[i] = 0; + user_jobs[i] = 0; + user_queue[i] = 0; + } + read_user_file(get_user_path()); set_socket_model(_path); @@ -368,6 +364,10 @@ static enum Break client_read(int index) { /* Process message */ switch (m.type) { + case REFRESH_USERS: + if (m.uid == getuid()) + read_user_file(get_user_path()); + break; case KILL_SERVER: if (m.uid == getuid()) return BREAK; /* break in the parent*/ @@ -405,6 +405,7 @@ static enum Break client_read(int index) { s_list_plain(s); else s_list(s); + s_user_status(s); /* We must actively close, meaning End of Lines */ close(s); remove_connection(index); @@ -414,7 +415,7 @@ static enum Break client_read(int index) { if (m.u.list.plain_list) s_list_plain(s); else - s_list(s); + s_list_all(s); s_user_status_all(s); /* We must actively close, meaning End of Lines */ close(s); diff --git a/user.c b/user.c index 1d35d4f..42b0ff8 100644 --- a/user.c +++ b/user.c @@ -1,12 +1,22 @@ +#include "user.h" #include #include +#include #include -#include "user.h" - void send_list_line(int s, const char *str); void error(const char *str, ...); +const char *get_user_path() { + char *str; + str = getenv("TS_USER_PATH"); + if (str == NULL || strlen(str) == 0) { + return "/home/kylin/task-spooler/user.txt"; + } else { + return str; + } +} + void read_user_file(const char *path) { server_uid = getuid(); if (server_uid != 0) { @@ -26,14 +36,15 @@ void read_user_file(const char *path) { int res = sscanf(line, "%d %256s %d", &user_UID[user_number], user_name[user_number], &user_max_slots[user_number]); - if (res != 3) + if (res != 3) { printf("error in read %s at line %s", path, line); - else { + exit(0); + } else { // printf("%d %s %d\n", user_ID[user_number], user_name[user_number], // user_slot[user_number]); - user_busy[user_number] = 0; - user_jobs[user_number] = 0; - user_queue[user_number] = 0; + // user_busy[user_number] = 0; + // user_jobs[user_number] = 0; + // user_queue[user_number] = 0; user_number++; } } diff --git a/user.txt b/user.txt index d715ad0..7ad3a69 100644 --- a/user.txt +++ b/user.txt @@ -1,3 +1,4 @@ #uid user maxslot 1000 Kylin 3 2 user 100 +34 user2 100 From e4ea8c1d6ddf50587ba0034f8255e5230bbe0598 Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 2 Aug 2022 17:22:38 +0800 Subject: [PATCH 05/91] version 0.0.6 --- jobs.c | 34 +++++++++++++++++++++++++++++++++- list.c | 46 ++++++++++++++++++++++++++++++---------------- main.h | 3 ++- server.c | 4 ++-- user.c | 4 ++-- 5 files changed, 69 insertions(+), 22 deletions(-) diff --git a/jobs.c b/jobs.c index cb652bf..29c8cd9 100644 --- a/jobs.c +++ b/jobs.c @@ -323,7 +323,39 @@ const char *jstate2string(enum Jobstate s) { return jobstate; } -void s_list(int s) { +void s_list(int s, int user_id) { + struct Job *p; + char *buffer; + + /* Times: 0.00/0.00/0.00 - 4+4+4+2 = 14*/ + buffer = joblist_headers(); + send_list_line(s, buffer); + free(buffer); + + /* Show Queued or Running jobs */ + p = firstjob; + while (p != 0) { + if (p->state != HOLDING_CLIENT) { + buffer = joblist_line(p); + if (p->user_id == user_id) + send_list_line(s, buffer); + free(buffer); + } + p = p->next; + } + + p = first_finished_job; + + /* Show Finished jobs */ + while (p != 0) { + buffer = joblist_line(p); + send_list_line(s, buffer); + free(buffer); + p = p->next; + } +} + +void s_list_all(int s) { struct Job *p; char *buffer; diff --git a/list.c b/list.c index 1596a24..06c2f9b 100644 --- a/list.c +++ b/list.c @@ -93,7 +93,8 @@ static char *print_noresult(const struct Job *p) { char *uname = user_name[p->user_id]; maxlen = 4 + 1 + 10 + 1 + 20 + 1 + 8 + 1 + 25 + 1 + strlen(p->command) + 20 + - strlen(uname) + 2; /* 20 is the margin for errors */ + strlen(uname) + 240 + + strlen(output_filename); /* 20 is the margin for errors */ if (p->label) maxlen += 3 + strlen(p->label); @@ -116,23 +117,35 @@ static char *print_noresult(const struct Job *p) { pos += snprintf(&dependstr[pos], sizeof(dependstr), "]&& "); } + struct timeval starttv = p->info.start_time; + struct timeval endtv; + gettimeofday(&endtv, NULL); + float real_ms = endtv.tv_sec - starttv.tv_sec + + ((float)(endtv.tv_usec - starttv.tv_usec) / 1000000.); + if (p->state == QUEUED) { + real_ms = 0; + } + char *unit = time_rep(&real_ms); + line = (char *)malloc(maxlen); if (line == NULL) error("Malloc for %i failed.\n", maxlen); cmd_len = max((strlen(p->command) + (term_width - maxlen)), 20); + char *cmd = shorten(p->command, cmd_len); if (p->label) { char *label = shorten(p->label, 20); - char *cmd = shorten(p->command, cmd_len); - snprintf(line, maxlen, "%-4i %-10s %-10s %-20s %-8s %6s %s[%s]%s\n", - p->jobid, jobstate, uname, output_filename, "", "", dependstr, - label, cmd); + snprintf(line, maxlen, "%-4i %-10s %-3i %-10s %s %5.2f%s %s | %-20s\n", + p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, + output_filename); free(label); free(cmd); } else { char *cmd = shorten(p->command, cmd_len); - snprintf(line, maxlen, "%-4i %-10s %-10s %-20s %-8s %6s %s%s\n", p->jobid, - jobstate, uname, output_filename, "", "", dependstr, cmd); + char *label = "(..)"; + snprintf(line, maxlen, "%-4i %-10s %-3i %-10s %s %5.2f%s %s | %-20s\n", + p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, + output_filename); free(cmd); } @@ -154,9 +167,9 @@ static char *print_result(const struct Job *p) { output_filename = ofilename_shown(p); char *uname = user_name[p->user_id]; - maxlen = 4 + 1 + 10 + 1 + 20 + 1 + 8 + 1 + 25 + 1 + strlen(p->command) + 20 + - strlen(uname) + 2; /* 20 is the margin for errors */ + strlen(uname) + 240 + + strlen(output_filename); /* 20 is the margin for errors */ if (p->label) maxlen += 3 + strlen(p->label); @@ -184,19 +197,20 @@ static char *print_result(const struct Job *p) { error("Malloc for %i failed.\n", maxlen); cmd_len = max((strlen(p->command) + (term_width - maxlen)), 20); + char *cmd = shorten(p->command, cmd_len); if (p->label) { char *label = shorten(p->label, 20); - char *cmd = shorten(p->command, cmd_len); - snprintf(line, maxlen, "%-4i %-10s %-10s %-20s %-8i %5.2f%s %s[%s]%s\n", - p->jobid, jobstate, uname, output_filename, p->result.errorlevel, - real_ms, unit, dependstr, label, cmd); + snprintf(line, maxlen, "%-4i %-10s %-3i %-10s %s %5.2f%s %s | %-20s\n", + p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, + output_filename); free(label); free(cmd); } else { char *cmd = shorten(p->command, cmd_len); - snprintf(line, maxlen, "%-4i %-10s %-10s %-20s %-8i %5.2f%s %s%s\n", - p->jobid, jobstate, uname, output_filename, p->result.errorlevel, - real_ms, unit, dependstr, cmd); + char *label = "(..)"; + snprintf(line, maxlen, "%-4i %-10s %-3i %-10s %s %5.2f%s %s | %-20s\n", + p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, + output_filename); free(cmd); } diff --git a/main.h b/main.h index 71709f5..66d6713 100644 --- a/main.h +++ b/main.h @@ -287,7 +287,8 @@ void c_set_env(); void c_unset_env(); /* jobs.c */ -void s_list(int s); +void s_list(int s, int user_id); +void s_list_all(int s); void s_list_plain(int s); diff --git a/server.c b/server.c index 9a5676a..9472087 100644 --- a/server.c +++ b/server.c @@ -404,8 +404,8 @@ static enum Break client_read(int index) { if (m.u.list.plain_list) s_list_plain(s); else - s_list(s); - s_user_status(s); + s_list(s, user_id); + s_user_status(s, user_id); /* We must actively close, meaning End of Lines */ close(s); remove_connection(index); diff --git a/user.c b/user.c index 42b0ff8..d886ea1 100644 --- a/user.c +++ b/user.c @@ -57,7 +57,7 @@ void read_user_file(const char *path) { void s_user_status_all(int s) { char buffer[256]; for (size_t i = 0; i < user_number; i++) { - snprintf(buffer, 256, "[%04d] %3d/%3d %20s %d\n", user_UID[i], user_busy[i], + snprintf(buffer, 256, "[%04d] %3d/%d %20s %d\n", user_UID[i], user_busy[i], user_max_slots[i], user_name[i], user_jobs[user_number]); send_list_line(s, buffer); } @@ -68,7 +68,7 @@ void s_user_status_all(int s) { void s_user_status(int s, int i) { char buffer[256]; - snprintf(buffer, 256, "[%04d] %3d/%3d %20s %d\n", user_UID[i], user_busy[i], + snprintf(buffer, 256, "[%04d] %3d/%d %20s %d\n", user_UID[i], user_busy[i], user_max_slots[i], user_name[i], user_jobs[user_number]); send_list_line(s, buffer); } From e4af58e060a553bba9a0ffeffe9ea7cdd9020504 Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 2 Aug 2022 18:17:36 +0800 Subject: [PATCH 06/91] version 0.1.0 --- execute.c | 495 +++++++++++++++++++++++++++--------------------------- list.c | 34 ++-- main.c | 2 +- ts.1 | 377 ----------------------------------------- user.c | 3 +- 5 files changed, 270 insertions(+), 641 deletions(-) delete mode 100644 ts.1 diff --git a/execute.c b/execute.c index 7dd5f42..bb4339b 100644 --- a/execute.c +++ b/execute.c @@ -4,20 +4,18 @@ Please find the license in the provided COPYING file. */ -#include -#include +#include +#include #include +#include +#include #include +#include +#include #include #include -#include -#include #include -#include -#include -#include -#include -#include +#include #include "main.h" @@ -26,258 +24,259 @@ extern int signals_child_pid; /* 0, not set. otherwise, set. */ /* Returns errorlevel */ static void run_parent(int fd_read_filename, int pid, struct Result *result) { - int status; - char *ofname = 0; - int namesize; - int res; - char *command; - struct timeval starttv; - struct timeval endtv; - struct tms cpu_times; - - /* Read the filename */ - /* This is linked with the write() in this same file, in run_child() */ - if (command_line.store_output) { - res = read(fd_read_filename, &namesize, sizeof(namesize)); - if (res == -1) - error("read the filename from %i", fd_read_filename); - if (res != sizeof(namesize)) - error("Reading the size of the name"); - ofname = (char *) malloc(namesize); - res = read(fd_read_filename, ofname, namesize); - if (res != namesize) - error("Reading the out file name"); - } - res = read(fd_read_filename, &starttv, sizeof(starttv)); - if (res != sizeof(starttv)) - error("Reading the the struct timeval"); - close(fd_read_filename); - - /* All went fine - prepare the SIGINT and send runjob_ok */ - signals_child_pid = pid; - unblock_sigint_and_install_handler(); - - c_send_runjob_ok(ofname, pid); - - wait(&status); - - /* Set the errorlevel */ - if (WIFEXITED(status)) { - /* We force the proper cast */ - signed char tmp; - tmp = WEXITSTATUS(status); - result->errorlevel = tmp; - result->died_by_signal = 0; - } else if (WIFSIGNALED(status)) { - signed char tmp; - tmp = WTERMSIG(status); - result->signal = tmp; - result->errorlevel = -1; - result->died_by_signal = 1; - } else { - result->died_by_signal = 0; - result->errorlevel = -1; - } - - command = build_command_string(); - if (command_line.send_output_by_mail) { - send_mail(command_line.jobid, result->errorlevel, ofname, command); - } - hook_on_finish(command_line.jobid, result->errorlevel, ofname, command); - free(command); - - free(ofname); - - /* Calculate times */ - gettimeofday(&endtv, NULL); - result->real_ms = endtv.tv_sec - starttv.tv_sec + - ((float) (endtv.tv_usec - starttv.tv_usec) / 1000000.); - times(&cpu_times); - /* The times are given in clock ticks. The number of clock ticks per second - * is obtained in POSIX using sysconf(). */ - result->user_ms = (float) cpu_times.tms_cutime / - (float) sysconf(_SC_CLK_TCK); - result->system_ms = (float) cpu_times.tms_cstime / - (float) sysconf(_SC_CLK_TCK); + int status; + char *ofname = 0; + int namesize; + int res; + char *command; + struct timeval starttv; + struct timeval endtv; + struct tms cpu_times; + + /* Read the filename */ + /* This is linked with the write() in this same file, in run_child() */ + if (command_line.store_output) { + res = read(fd_read_filename, &namesize, sizeof(namesize)); + if (res == -1) + error("read the filename from %i", fd_read_filename); + if (res != sizeof(namesize)) + error("Reading the size of the name"); + ofname = (char *)malloc(namesize); + res = read(fd_read_filename, ofname, namesize); + if (res != namesize) + error("Reading the out file name"); + } + res = read(fd_read_filename, &starttv, sizeof(starttv)); + if (res != sizeof(starttv)) + error("Reading the the struct timeval"); + close(fd_read_filename); + + /* All went fine - prepare the SIGINT and send runjob_ok */ + signals_child_pid = pid; + unblock_sigint_and_install_handler(); + + c_send_runjob_ok(ofname, pid); + + wait(&status); + + /* Set the errorlevel */ + if (WIFEXITED(status)) { + /* We force the proper cast */ + signed char tmp; + tmp = WEXITSTATUS(status); + result->errorlevel = tmp; + result->died_by_signal = 0; + } else if (WIFSIGNALED(status)) { + signed char tmp; + tmp = WTERMSIG(status); + result->signal = tmp; + result->errorlevel = -1; + result->died_by_signal = 1; + } else { + result->died_by_signal = 0; + result->errorlevel = -1; + } + + command = build_command_string(); + if (command_line.send_output_by_mail) { + send_mail(command_line.jobid, result->errorlevel, ofname, command); + } + hook_on_finish(command_line.jobid, result->errorlevel, ofname, command); + free(command); + + free(ofname); + + /* Calculate times */ + gettimeofday(&endtv, NULL); + result->real_ms = endtv.tv_sec - starttv.tv_sec + + ((float)(endtv.tv_usec - starttv.tv_usec) / 1000000.); + times(&cpu_times); + /* The times are given in clock ticks. The number of clock ticks per second + * is obtained in POSIX using sysconf(). */ + result->user_ms = (float)cpu_times.tms_cutime / (float)sysconf(_SC_CLK_TCK); + result->system_ms = (float)cpu_times.tms_cstime / (float)sysconf(_SC_CLK_TCK); } void create_closed_read_on(int dest) { - int p[2]; - /* Closing input */ - pipe(p); - close(p[1]); /* closing the write handle */ - dup2(p[0], dest); /* the pipe reading goes to dest */ - if (p[0] != dest) - close(p[0]); + int p[2]; + /* Closing input */ + pipe(p); + close(p[1]); /* closing the write handle */ + dup2(p[0], dest); /* the pipe reading goes to dest */ + if (p[0] != dest) + close(p[0]); } /* This will close fd_out and fd_in in the parent */ static void run_gzip(int fd_out, int fd_in) { - int pid; - pid = fork(); - - switch (pid) { - case 0: /* child */ - restore_sigmask(); - dup2(fd_in, 0); /* stdout */ - dup2(fd_out, 1); /* stdout */ - close(fd_in); - close(fd_out); - /* Without stderr */ - close(2); - execlp("gzip", "gzip", NULL); - exit(-1); - /* Won't return */ - case -1: - exit(-1); /* Fork error */ - default: - close(fd_in); - close(fd_out); - } + int pid; + pid = fork(); + + switch (pid) { + case 0: /* child */ + restore_sigmask(); + dup2(fd_in, 0); /* stdout */ + dup2(fd_out, 1); /* stdout */ + close(fd_in); + close(fd_out); + /* Without stderr */ + close(2); + execlp("gzip", "gzip", NULL); + exit(-1); + /* Won't return */ + case -1: + exit(-1); /* Fork error */ + default: + close(fd_in); + close(fd_out); + } } -static void run_child(int fd_send_filename, const char* tmpdir) { - char *outfname; - char errfname[sizeof outfname + 2]; /* .e */ - int namesize; - int outfd; - int err; - struct timeval starttv; - - if (command_line.logfile) { - outfname = malloc(1 + strlen(command_line.logfile) + strlen(".XXXXXX") + 1); - sprintf(outfname, "/%s.XXXXXX", command_line.logfile); - } else - outfname = "/ts-out.XXXXXX"; - - if (command_line.store_output) { - /* Prepare path */ - int lname; - char *outfname_full; - - if (tmpdir == NULL) - tmpdir = "/tmp"; - lname = strlen(tmpdir) + strlen(outfname) + 1 /* \0 */; - - outfname_full = (char *) malloc(lname); - strcpy(outfname_full, tmpdir); - strcat(outfname_full, outfname); - - if (command_line.gzip) { - int p[2]; - /* We assume that all handles are closed*/ - err = pipe(p); - assert(err == 0); - - /* gzip output goes to the filename */ - /* This will be the handle other than 0,1,2 */ - /* mkstemp doesn't admit adding ".gz" to the pattern */ - outfd = mkstemp(outfname_full); /* stdout */ - assert(outfd != -1); - - /* Program stdout and stderr */ - /* which go to pipe write handle */ - err = dup2(p[1], 1); - assert(err != -1); - if (command_line.stderr_apart) { - int errfd; - strncpy(errfname, outfname_full, sizeof errfname); - strncat(errfname, ".e", 2 + 1); - errfd = open(errfname, O_CREAT | O_WRONLY | O_TRUNC, 0600); - assert(err == 0); - err = dup2(errfd, 2); - assert(err == 0); - err = close(errfd); - assert(err == 0); - } else { - err = dup2(p[1], 2); - assert(err != -1); - } - err = close(p[1]); - assert(err == 0); - - /* run gzip. - * This wants p[0] in 0, so gzip will read - * from it */ - run_gzip(outfd, p[0]); - } else { - /* Prepare the filename */ - outfd = mkstemp(outfname_full); /* stdout */ - dup2(outfd, 1); /* stdout */ - if (command_line.stderr_apart) { - int errfd; - strncpy(errfname, outfname_full, sizeof errfname); - strncat(errfname, ".e", 2 + 1); - errfd = open(errfname, O_CREAT | O_WRONLY | O_TRUNC, 0600); - dup2(errfd, 2); - close(errfd); - } else - dup2(outfd, 2); - close(outfd); - } - - /* Send the filename */ - namesize = strlen(outfname_full) + 1; - write(fd_send_filename, (char *) &namesize, sizeof(namesize)); - write(fd_send_filename, outfname_full, namesize); - free(outfname_full); +static void run_child(int fd_send_filename, const char *tmpdir) { + char *outfname; + char errfname[sizeof outfname + 2]; /* .e */ + int namesize; + int outfd; + int err; + struct timeval starttv; + + if (command_line.label) { + outfname = malloc(1 + strlen(command_line.label) + strlen(".XXXXXX") + 1); + sprintf(outfname, "/%s.XXXXXX", command_line.label); + } else + outfname = "/ts-out.XXXXXX"; + + if (command_line.store_output) { + /* Prepare path */ + int lname; + char *outfname_full; + + // if (tmpdir == NULL) + // tmpdir = "/tmp"; + lname = strlen(outfname) + strlen(tmpdir) + 1 /* \0 */; + + outfname_full = (char *)malloc(lname); + strcpy(outfname_full, tmpdir); + strcat(outfname_full, outfname); + + if (command_line.gzip) { + int p[2]; + /* We assume that all handles are closed*/ + err = pipe(p); + assert(err == 0); + + /* gzip output goes to the filename */ + /* This will be the handle other than 0,1,2 */ + /* mkstemp doesn't admit adding ".gz" to the pattern */ + outfd = mkstemp(outfname_full); /* stdout */ + assert(outfd != -1); + + /* Program stdout and stderr */ + /* which go to pipe write handle */ + err = dup2(p[1], 1); + assert(err != -1); + if (command_line.stderr_apart) { + int errfd; + strncpy(errfname, outfname_full, sizeof errfname); + strncat(errfname, ".e", 2 + 1); + errfd = open(errfname, O_CREAT | O_WRONLY | O_TRUNC, 0600); + assert(err == 0); + err = dup2(errfd, 2); + assert(err == 0); + err = close(errfd); + assert(err == 0); + } else { + err = dup2(p[1], 2); + assert(err != -1); + } + err = close(p[1]); + assert(err == 0); + + /* run gzip. + * This wants p[0] in 0, so gzip will read + * from it */ + run_gzip(outfd, p[0]); + } else { + /* Prepare the filename */ + outfd = mkstemp(outfname_full); /* stdout */ + dup2(outfd, 1); /* stdout */ + if (command_line.stderr_apart) { + int errfd; + strncpy(errfname, outfname_full, sizeof errfname); + strncat(errfname, ".e", 2 + 1); + errfd = open(errfname, O_CREAT | O_WRONLY | O_TRUNC, 0600); + dup2(errfd, 2); + close(errfd); + } else + dup2(outfd, 2); + close(outfd); } - /* Times */ - gettimeofday(&starttv, NULL); - write(fd_send_filename, &starttv, sizeof(starttv)); - close(fd_send_filename); - - /* Closing input */ - if (command_line.should_go_background) - create_closed_read_on(0); - - /* We create a new session, so we can kill process groups as: - kill -- -`ts -p` */ - setsid(); - execvp(command_line.command.array[0], command_line.command.array); + + /* Send the filename */ + namesize = strlen(outfname_full) + 1; + write(fd_send_filename, (char *)&namesize, sizeof(namesize)); + write(fd_send_filename, outfname_full, namesize); + free(outfname_full); + } + /* Times */ + gettimeofday(&starttv, NULL); + write(fd_send_filename, &starttv, sizeof(starttv)); + close(fd_send_filename); + + /* Closing input */ + if (command_line.should_go_background) + create_closed_read_on(0); + + /* We create a new session, so we can kill process groups as: + kill -- -`ts -p` */ + setsid(); + execvp(command_line.command.array[0], command_line.command.array); } int run_job(struct Result *res) { - int pid; - int errorlevel; - int p[2]; - const char *tmpdir = get_logdir(); - - /* For the parent */ - /*program_signal(); Still not needed*/ - - block_sigint(); - - /* Prepare the output filename sending */ - pipe(p); - - pid = fork(); - - switch (pid) { - case 0: - restore_sigmask(); - close(server_socket); - close(p[0]); - run_child(p[1], tmpdir); - /* Not reachable, if the 'exec' of the command - * works. Thus, command exists, etc. */ - fprintf(stderr, "ts could not run the command\n"); - free((char*) tmpdir); - exit(-1); - /* To avoid a compiler warning */ - errorlevel = 0; - break; - case -1: - /* To avoid a compiler warning */ - errorlevel = 0; - error("forking"); - default: - close(p[1]); - run_parent(p[0], pid, res); - break; - } - free((char*) tmpdir); - return errorlevel; + int pid; + int errorlevel; + int p[2]; + char path[256]; + getcwd(path, 256); + // const char *tmpdir = get_logdir(); + // printf("tmpdir: %s\n", tmpdir); + + /* For the parent */ + /*program_signal(); Still not needed*/ + + block_sigint(); + + /* Prepare the output filename sending */ + pipe(p); + + pid = fork(); + + switch (pid) { + case 0: + restore_sigmask(); + close(server_socket); + close(p[0]); + run_child(p[1], path); + /* Not reachable, if the 'exec' of the command + * works. Thus, command exists, etc. */ + fprintf(stderr, "ts could not run the command\n"); + // free((char *)tmpdir); + exit(-1); + /* To avoid a compiler warning */ + errorlevel = 0; + break; + case -1: + /* To avoid a compiler warning */ + errorlevel = 0; + error("forking"); + default: + close(p[1]); + run_parent(p[0], pid, res); + break; + } + // free((char *)tmpdir); + return errorlevel; } #if 0 diff --git a/list.c b/list.c index 06c2f9b..36a61af 100644 --- a/list.c +++ b/list.c @@ -48,9 +48,10 @@ char *joblist_headers() { char *line; line = malloc(100); - snprintf(line, 100, "%-4s %-10s %-20s %-8s %-6s %s [run=%i/%i]\n", "ID", - "State", "Output", "E-Level", "Time", "Command", busy_slots, - max_slots); + snprintf(line, 100, + "%-4s %-7s %-7s %-6s %-10s %6s %-20s %s [run=%i/%i %.2f%%]\n", + "ID", "State", "Proc.", "User", "Label", "Time", "Command", "Log", + busy_slots, max_slots, 100.0 * busy_slots / max_slots); return line; } @@ -71,7 +72,8 @@ static const char *ofilename_shown(const struct Job *p) { * problems */ output_filename = "(...)"; else - output_filename = shorten(p->output_filename, 20); + output_filename = + p->output_filename; // shorten(p->output_filename, 20); } } else output_filename = "stdout"; @@ -119,13 +121,17 @@ static char *print_noresult(const struct Job *p) { struct timeval starttv = p->info.start_time; struct timeval endtv; - gettimeofday(&endtv, NULL); - float real_ms = endtv.tv_sec - starttv.tv_sec + - ((float)(endtv.tv_usec - starttv.tv_usec) / 1000000.); + float real_ms; + char *unit; if (p->state == QUEUED) { real_ms = 0; + unit = " "; + } else { + gettimeofday(&endtv, NULL); + real_ms = endtv.tv_sec - starttv.tv_sec + + ((float)(endtv.tv_usec - starttv.tv_usec) / 1000000.); + unit = time_rep(&real_ms); } - char *unit = time_rep(&real_ms); line = (char *)malloc(maxlen); if (line == NULL) @@ -134,8 +140,8 @@ static char *print_noresult(const struct Job *p) { cmd_len = max((strlen(p->command) + (term_width - maxlen)), 20); char *cmd = shorten(p->command, cmd_len); if (p->label) { - char *label = shorten(p->label, 20); - snprintf(line, maxlen, "%-4i %-10s %-3i %-10s %s %5.2f%s %s | %-20s\n", + char *label = shorten(p->label, 10); + snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-20s | %s\n", p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, output_filename); free(label); @@ -143,7 +149,7 @@ static char *print_noresult(const struct Job *p) { } else { char *cmd = shorten(p->command, cmd_len); char *label = "(..)"; - snprintf(line, maxlen, "%-4i %-10s %-3i %-10s %s %5.2f%s %s | %-20s\n", + snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-20s | %s\n", p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, output_filename); free(cmd); @@ -199,8 +205,8 @@ static char *print_result(const struct Job *p) { cmd_len = max((strlen(p->command) + (term_width - maxlen)), 20); char *cmd = shorten(p->command, cmd_len); if (p->label) { - char *label = shorten(p->label, 20); - snprintf(line, maxlen, "%-4i %-10s %-3i %-10s %s %5.2f%s %s | %-20s\n", + char *label = shorten(p->label, 10); + snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-20s | %s\n", p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, output_filename); free(label); @@ -208,7 +214,7 @@ static char *print_result(const struct Job *p) { } else { char *cmd = shorten(p->command, cmd_len); char *label = "(..)"; - snprintf(line, maxlen, "%-4i %-10s %-3i %-10s %s %5.2f%s %s | %-20s\n", + snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-20s | %s\n", p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, output_filename); free(cmd); diff --git a/main.c b/main.c index b911913..7e38bd3 100644 --- a/main.c +++ b/main.c @@ -67,7 +67,7 @@ static void default_command_line() { command_line.stderr_apart = 0; command_line.num_slots = 1; command_line.require_elevel = 0; - command_line.logfile = 0; + command_line.logfile = NULL; } struct Msg default_msg() { diff --git a/ts.1 b/ts.1 deleted file mode 100644 index 28507e6..0000000 --- a/ts.1 +++ /dev/null @@ -1,377 +0,0 @@ -.\" Copyright Lluís Batlle i Rossell -.\" -.\" This file may be copied under the conditions described -.\" in the LDP GENERAL PUBLIC LICENSE, Version 1, September 1998 -.\" that should have been distributed together with this file. -.\" -.\" Note: I took the gnu 'ls' man page as an example. -.TH TS 1 2021-05 "Task Spooler 1.2" -.SH NAME -ts \- task spooler. A simple unix batch system -.SH SYNOPSIS -.BI "ts [" actions "] [" options "] [" command... ] -.sp -Actions: -.BI "[\-KClhVTRq] -.BI "[\-t ["id ]] -.BI "[\-c ["id ]] -.BI "[\-p ["id ]] -.BI "[\-o ["id ]] -.BI "[\-s ["id ]] -.BI "[\-r ["id ]] -.BI "[\-w ["id ]] -.BI "[\-k ["id ]] -.BI "[\-u ["id ]] -.BI "[\-i ["id ]] -.BI "[\-U <"id - id >] -.BI "[\-S ["num ]] -.BI "[\-a/--get_label ["id ]] -.BI "[\-F/--full_cmd ["id ]] -.BI "[\--getenv ["var ]] -.BI "[\--setenv ["var=val ]] -.BI "[\--unsetenv ["var ]] -.BI "[\--get_logdir] -.BI "[\--set_logdir ["path ]] -.BI "[\--plain] - -.sp -Options: -.BI "[\-nEfzmd]" -.BI "[\-L <"label >] -.BI "[\-D <"id1,id2,... >] -.BI "[\-O ["name ]] - -.SH DESCRIPTION -.B ts -will run by default a per user unix task queue. The user can add commands to -the queue, watch that queue at any moment, and look at the task results -(actually, standard output and exit error). -.SH SIMPLE USE -Calling -.B ts -with a command will add that command to the queue, and calling it without -commands or parameters will show the task list. -.SH COMMAND OPTIONS -When adding a job to ts, we can specify how it will be run and how the -results will be collected: -.TP -.B "\-n" -Do not store the standard output/error in a file at -.B $TMPDIR -- let it use the -file descriptors decided by the calling process. If it is not used, the -.B jobid -for the new task will be output to stdout. -.TP -.B "\-z" -Pass the output through gzip (only if -.B \-n -). Note that the output files will not -have a .gz extension. -.TP -.B "\-f" -Don not put the task into background. Wait the queue and the command run without -getting detached of the terminal. The exit code will be that of the command, and -if used together with \-n, no result will be stored in the queue. -.TP -.B "\-m" -Mail the results of the command (output and exit code) to -.B $TS_MAILTO -, or to the -.B $USER -using -.B /usr/sbin/sendmail. -Look at -.B ENVIRONMENT. -.TP -.B "\-L [label]" -Add a label to the task, which will appear next to its command when listing -the queue. It makes more comfortable distinguishing similar commands with -different goals. -.TP -.B "\-d" -Run the command only after the last command finished. -It does not depend on how its dependency ends. -.TP -.B "\-D [id,...]" -Run the command only after the specified job IDs finished. -It does not depend on how its dependencies end. -.TP -.B "\-W [id,...]" -Run the command only if the job of given id finished well (errorlevel = 0). This new -task enqueued depends on the result of the previous command. If the task is not run, -it is considered as failed for further dependencies. -If the server doesn't have the job id in its list, it will be considered -as if the job failed. -.TP -.B "\-B" -In the case the queue is full (due to \fBTS_MAXCONN\fR or system limits), -by default ts will block the enqueuing command. Using \fB\-B\fR, -if the queue is full it will exit returning the value 2 instead of blocking. -.TP -.B "\-E" -Keep two different output files for the command stdout and stderr. stdout goes to -the file announced by ts (look at \fB\-o\fR), and stderr goes to the stdout file -with an additional ".e". For example, /tmp/ts-out.SKsDw8 and /tmp/ts-out.SKsDw8.e. -Only the stdout file gets created with \fBmkstemp\fR, ensuring it does not overwrite -any other; the ".e" will be overwritten if it existed. -.TP -.B "\-O [name]" -Set the log name to the specified name. Do not include any path in the specified name. -.TP -.B "\-N [num]" -Run the command only if there are \fbnum\fB slots free in the queue. Without it, -the job will run if there is one slot free. For example, if you use the -queue to feed cpu cores, and you know that a job will take two cores, with \fB\-N\fB -you can let ts know that. -.SH ACTIONS -Instead of giving a new command, we can use the parameters for other purposes: -.TP -.B "\--getenv [var]" -Get the specified environment variable value from the -.B ts -server. -.TP -.B "\--setenv [var]" -Set the specified environment variable to the -.B ts -server. -.TP -.B "\--unsetenv [var]" -Remove the specified environment variable from the -.B ts -server. -.TP -.B "\-K" -Kill the -.B ts -server for the calling client. This will remove the unix socket and -all the -.B ts -processes related to the queue. This will not kill the command being -run at that time. - -It is not reliable to think that -.B ts -K -will finish when the server is really killed. By now it is a race condition. -.TP -.B "\-T" -Send SIGTERM to all running job groups. -.TP -.B "\-C" -Clear the results of finished jobs from the queue. -.TP -.B "\-l" -Show the list of jobs - to be run, running and finished - for the current queue. -This is the default behaviour if -.B ts -is called without options. -.TP -.B "\--plain" -Show the list of jobs - to be run, running and finished - for the current queue in tab-separated plain texts. -.TP -.B "\-q/--last_queue_id" -Show the job ID of the last added. -.TP -.B "\-R/--count_running" -Return the number of running jobs -.TP -.B "\-a/--get_label " -Show the job label. Of the last added, if not specified. -.TP -.B "\-F/--full_cmd [id]" -Show the full command. Of the last added, if not specified. -.TP -.B "\--get_logdir" -Show the path containing log files. -.TP -.B "\--get_logdir [path]" -Set the path containing log files to the specified path. -.TP -.B "\-t [id]" -Show the last ten lines of the output file of the named job, or the last -running/run if not specified. If the job is still running, it will keep on -showing the additional output until the job finishes. On exit, it returns the -errorlevel of the job, as in \fB\-c\fR. -.TP -.B "\-c [id]" -Run the system's cat to the output file of the named job, or the last -running/run if not specified. It will block until all the output can be -sent to standard output, and will exit with the job errorlevel as in -\fB\-c\fR. -.TP -.B "\-p [id]" -Show the pid of the named job, or the last running/run if not specified. -.TP -.B "\-o [id]" -Show the output file name of the named job, or the last running/run -if not specified. -.TP -.B "\-s [id]" -Show the job state of the named job, or the last in the queue. -.TP -.B "\-r [id]" -Remove the named job, or the last in the queue. -.TP -.B "\-w [id]" -Wait for the named job, or for the last in the queue. -.TP -.B "\-k [id]" -Kill the process group of the named job (SIGTERM), -or the last running/run job if not specified. -Equivalent to -.B kill -- -`ts -p` -.TP -.B "\-u [id]" -Make the named job (or the last in the queue) urgent - this means that it goes -forward in the queue so it can run as soon as possible. -.TP -.B "\-i [id]" -Show information about the named job (or the last run). It will show the command line, -some times related to the task, and also any information resulting from -\fBTS_ENV\fR (Look at \fBENVIRONMENT\fR). -.TP -.B "\-U " -Interchange the queue positions of the named jobs (separated by a hyphen and no -spaces). -.TP -.B "\-h" -Show help on standard output. -.TP -.B "\-V" -Show the program version. -.SH MULTI-SLOT -.B ts -by default offers a queue where each job runs only after the previous finished. -Nevertheless, you can change the maximum number of jobs running at once with -the -.B "\-S [num]" -parameter. We call that number the -\fIamount of slots\fR. You can also set the initial number of jobs with -the environment variable -.B "TS_SLOTS". -When increasing this setting, queued waiting jobs will be run -at once until reaching the maximum set. When decreasing this setting, no other -job will be run until it can meet the amount of running jobs set. -.BR -When using an amount of slots greater than 1, the action of some commands -may change a bit. For example, \fB\-t\fR without \fIjobid\fR will tail the first -job running, and \fB\-d\fR will try to set the dependency with the last job added. -.TP -.B "\-S [num]" -Set the maximum amount of running jobs at once. If you don't specify -.B num -it will return the maximum amount of running jobs set. - - -.SH ENVIRONMENT -.TP -.B "TS_MAXFINISHED" -Limit the number of job results (finished tasks) you want in the queue. Use this -option if you are tired of -.B \-C. -.TP -.B "TS_MAXCONN" -The maximum number of ts server connections to clients. This will make the ts clients -block until connections are freed. This helps, for example, on systems with a limited -number of processes, because each job waiting in the queue remains as a process. This -variable has to be set at server start, and cannot be modified later. -.TP -.B "TS_ONFINISH" -If the variable exists pointing to an executable, it will be run by the client -after the queued job. It uses execlp, so -.B PATH -is used if there are no slashes in the variable content. The executable is run -with four parameters: -.B jobid -.B errorlevel -.B output_filename -and -.B command. -.TP -.B "TMPDIR" -As the program output and the unix socket are thought to be stored in a -temporary directory, -.B TMPDIR -will be used if defined, or -.B /tmp -otherwise. -.TP -.B "TS_SOCKET" -Each queue has a related unix socket. You can specify the socket path with this -environment variable. This way, you can have a queue for your heavy disk -operations, another for heavy use of ram., and have a simple script/alias -wrapper over ts for those special queues. If it is not specified, it will be -.B $TMPDIR/socket-ts.[uid]. -.TP -.B "TS_SLOTS" -Set the number of slots at the start of the server, similar to -.B \-S, -but the contents of the variable are read only when running -the first instance of -.B ts. -.TP -.B "TS_MAILTO" -Send the letters with job results to the address specified in this variable. -Otherwise, they are sent to -.B $USER -or if not defined, -.B nobody. -The system -.B /usr/sbin/sendmail -is used. The -job outputs are not sent as an attachment, so understand the consequences if you -use the -.B \-gm -flags together. -.TP -.B "USER" -As seen above, it is used for the mail destination if -.B TS_MAILTO -is not specified. -.TP -.B "TS_SAVELIST" -If it is defined when starting the queue server (probably the first -.B ts -command run), on SIGTERM the queue status will be saved to the file pointed -by this environment variable - for example, at system shutdown. -.TP -.B "TS_ENV" -This has a command to be run at enqueue time through -\fB/bin/sh\fR. The output of the command will be readable through the option -\fB\-i\fR. You can use a command which shows relevant environment for the command run. -For example, you may use \fBTS_ENV='pwd;set;mount'\fR. -.SH FILES -.TP -.B /tmp/ts.error -if -.B ts -finds any internal problem, you should find an error report there. -Please send this to the author as part of the bug report. - -.SH BUGS -.B ts -expects a simple command line. It does not start a shell parser. -If you want to run complex shell commands, you may want to run them through -.B sh -c 'commands...' -Also, remember that stdin/stdout/stderr will be detached, so -do not use your shell's redirection operators when you put a job into background. -You can use them inside the -.B sh -c -in order to set redirections to the command run. - -If an internal problem is found in runtime, a file -.B /tmp/ts.error -is created, which you can submit to the developer in order to fix the bug. - -.SH SEE ALSO -.BR at (1) -.SH AUTHOR -Duc Nguyen and Lluis Batlle i Rossell -.SH NOTES -This page describes -.B ts -as in version 1.2. Other versions may differ. The file -.B TRICKS -found in the distribution package can show some ideas on special uses of -.B ts. diff --git a/user.c b/user.c index d886ea1..818de96 100644 --- a/user.c +++ b/user.c @@ -1,4 +1,5 @@ #include "user.h" +#define _GNU_SOURCE #include #include #include @@ -56,6 +57,7 @@ void read_user_file(const char *path) { void s_user_status_all(int s) { char buffer[256]; + send_list_line(s, "\n"); for (size_t i = 0; i < user_number; i++) { snprintf(buffer, 256, "[%04d] %3d/%d %20s %d\n", user_UID[i], user_busy[i], user_max_slots[i], user_name[i], user_jobs[user_number]); @@ -67,7 +69,6 @@ void s_user_status_all(int s) { void s_user_status(int s, int i) { char buffer[256]; - snprintf(buffer, 256, "[%04d] %3d/%d %20s %d\n", user_UID[i], user_busy[i], user_max_slots[i], user_name[i], user_jobs[user_number]); send_list_line(s, buffer); From ab5330af61f4f48028df9438128f8fc1503d4341 Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 2 Aug 2022 18:28:32 +0800 Subject: [PATCH 07/91] version 0.1.1 --- main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main.c b/main.c index 7e38bd3..b49cdec 100644 --- a/main.c +++ b/main.c @@ -460,6 +460,8 @@ static void print_help(const char *cmd) { "dies.\n"); printf(" TS_SLOTS amount of jobs which can run at once, read on server " "start.\n"); + printf(" TS_USER_PATH path to the user configuration file, read on server " + "starts.\n"); printf(" TMPDIR directory where to place the output files and the " "default socket.\n"); printf("Long option actions:\n"); @@ -484,6 +486,8 @@ static void print_help(const char *cmd) { printf(" --plain list jobs in plain tab-separated " "texts.\n"); printf("Actions:\n"); + printf(" -A Show all users information\n"); + printf(" -X Refresh the user configuration\n"); printf(" -K kill the task spooler server\n"); printf(" -C clear the list of finished jobs\n"); printf(" -l show the job list (default action)\n"); From 23d1c8f1c28c3a3bf2699a3e055e72c31df931cf Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 2 Aug 2022 23:08:59 +0800 Subject: [PATCH 08/91] version 0.1.2 --- client.c | 13 +++++++++++++ execute.c | 14 +++++++++----- jobs.c | 5 +++++ main.c | 8 +++++++- main.h | 2 +- server.c | 7 +++++-- user.c | 23 +++++++++++++++++++---- user.txt | 2 +- 8 files changed, 60 insertions(+), 14 deletions(-) diff --git a/client.c b/client.c index 6dbef8a..02f1bfd 100644 --- a/client.c +++ b/client.c @@ -329,7 +329,20 @@ static void c_end_of_job(const struct Result *res) { } void c_shutdown_server() { + struct Msg m = default_msg(); + if (m.uid != 0) { + printf("Only the root can shutdown the ts server\n"); + return; + } + char buf[10]; + printf("Do you want to kill the taskspooler server? (Yes/n) "); + scanf("%3s", buf); + if (strcmp(buf, "Yes") != 0) { + return; + } else { + printf("Kill the server!\n"); + } m.type = KILL_SERVER; send_msg(server_socket, &m); diff --git a/execute.c b/execute.c index bb4339b..e8544b2 100644 --- a/execute.c +++ b/execute.c @@ -139,12 +139,16 @@ static void run_child(int fd_send_filename, const char *tmpdir) { int outfd; int err; struct timeval starttv; + char *label = "ts_out"; + if (command_line.logfile) { + label = command_line.logfile; + } else if (command_line.label) { + label = command_line.label; + } + + outfname = malloc(1 + strlen(label) + strlen(".XXXXXX") + 1); - if (command_line.label) { - outfname = malloc(1 + strlen(command_line.label) + strlen(".XXXXXX") + 1); - sprintf(outfname, "/%s.XXXXXX", command_line.label); - } else - outfname = "/ts-out.XXXXXX"; + sprintf(outfname, "/%s.XXXXXX", label); if (command_line.store_output) { /* Prepare path */ diff --git a/jobs.c b/jobs.c index 29c8cd9..c638d69 100644 --- a/jobs.c +++ b/jobs.c @@ -995,6 +995,11 @@ void s_send_last_id(int s) { send_msg(s, &m); } +void s_refresh_users(int s) { + read_user_file(get_user_path()); + send_list_line(s, "refresh the list success!\n"); +} + void s_send_output(int s, int jobid) { struct Job *p = 0; struct Msg m = default_msg(); diff --git a/main.c b/main.c index b49cdec..0ba5b99 100644 --- a/main.c +++ b/main.c @@ -581,7 +581,13 @@ int main(int argc, char **argv) { switch (command_line.request) { case c_REFRESH_USER: - c_refresh_user(); + if (client_uid == 0) { + c_refresh_user(); + c_wait_server_lines(); + } else { + printf("Only the root can shutdown the task-spooler server\n"); + } + break; case c_SHOW_VERSION: print_version(); diff --git a/main.h b/main.h index 66d6713..0fa6794 100644 --- a/main.h +++ b/main.h @@ -487,6 +487,6 @@ const char *get_user_path(); /* jobs.c */ void s_user_status_all(int s); void s_user_status(int s, int i); - +void s_refresh_users(int s); /* client.c */ void c_list_jobs_all(); \ No newline at end of file diff --git a/server.c b/server.c index 9472087..ee1c9c8 100644 --- a/server.c +++ b/server.c @@ -198,6 +198,7 @@ void server_main(int notify_fd, char *_path) { if (res == -1) error("Error listening."); + user_number = 0; for (int i = 0; i < USER_MAX; i++) { user_busy[i] = 0; user_jobs[i] = 0; @@ -365,8 +366,10 @@ static enum Break client_read(int index) { /* Process message */ switch (m.type) { case REFRESH_USERS: - if (m.uid == getuid()) - read_user_file(get_user_path()); + if (m.uid == getuid()) { + s_refresh_users(s); + } + close(s); break; case KILL_SERVER: if (m.uid == getuid()) diff --git a/user.c b/user.c index 818de96..671c8fd 100644 --- a/user.c +++ b/user.c @@ -23,7 +23,6 @@ void read_user_file(const char *path) { if (server_uid != 0) { error("the service is not run as root!"); } - user_number = 0; FILE *fp; fp = fopen(path, "r"); if (fp == NULL) @@ -31,25 +30,41 @@ void read_user_file(const char *path) { char *line = NULL; size_t len = 0; size_t read; + int UID, slots; + char name[USER_NAME_WIDTH]; + + int i_number = 0; while ((read = getline(&line, &len, fp)) != -1) { if (line[0] == '#') continue; - int res = sscanf(line, "%d %256s %d", &user_UID[user_number], - user_name[user_number], &user_max_slots[user_number]); + int res = sscanf(line, "%d %256s %d", &UID, name, &slots); if (res != 3) { printf("error in read %s at line %s", path, line); exit(0); } else { + if (user_max_slots[i_number] != 0 && user_UID[i_number] != UID) { + i_number++; + continue; + } + + user_UID[i_number] = UID; + user_max_slots[i_number] = slots; + strncpy(user_name[i_number], name, USER_NAME_WIDTH); + // printf("%d %s %d\n", user_ID[user_number], user_name[user_number], // user_slot[user_number]); // user_busy[user_number] = 0; // user_jobs[user_number] = 0; // user_queue[user_number] = 0; - user_number++; + i_number++; } } + if (i_number > user_number) { + user_number = i_number; + } + fclose(fp); if (line) free(line); diff --git a/user.txt b/user.txt index 7ad3a69..5de7503 100644 --- a/user.txt +++ b/user.txt @@ -1,4 +1,4 @@ #uid user maxslot 1000 Kylin 3 2 user 100 -34 user2 100 +34 user2 30 From 06e13fc1b4a3b349253ffb4cdbfec02a1b111426 Mon Sep 17 00:00:00 2001 From: kylin Date: Wed, 3 Aug 2022 13:38:47 +0800 Subject: [PATCH 09/91] version 0.1.3 --- jobs.c | 14 ++++++++++---- main.c | 4 ++++ main.h | 4 ++++ server.c | 1 + user.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 66 insertions(+), 5 deletions(-) diff --git a/jobs.c b/jobs.c index c638d69..81722e2 100644 --- a/jobs.c +++ b/jobs.c @@ -31,7 +31,7 @@ struct Notify { /* Globals */ static struct Job *firstjob = 0; static struct Job *first_finished_job = 0; -static int jobids = 0; +static int jobids = 1000; /* This is used for dependencies from jobs * already out of the queue */ static int last_errorlevel = 0; /* Before the first job, let's consider @@ -48,6 +48,7 @@ static struct Job *get_job(int jobid); void notify_errorlevel(struct Job *p); +void set_jobids(int i) { jobids = i; } static void destroy_job(struct Job *p) { free(p->notify_errorlevel_to); free(p->command); @@ -893,6 +894,7 @@ void s_process_runjob_ok(int jobid, char *oname, int pid) { p->pid = pid; p->output_filename = oname; pinfo_set_start_time(&p->info); + write_logfile(p); } void s_send_runjob(int s, int jobid) { @@ -1125,7 +1127,11 @@ int s_remove_job(int s, int *jobid, int client_uid) { } // if (p == NULL || p == firstjob || user_UID[p->user_id] != client_uid) { - if (p == NULL || p == firstjob || user_UID[p->user_id] != client_uid) { + if (p != NULL && client_uid == 0) { + client_uid = user_UID[p->user_id]; + } + + if (p == NULL || p == firstjob || (user_UID[p->user_id] != client_uid)) { char tmp[256]; @@ -1140,8 +1146,8 @@ int s_remove_job(int s, int *jobid, int client_uid) { if (p != NULL) { int id = p->user_id; if (user_UID[id] != client_uid) { - snprintf(tmp, 256, "The job %i belongs to user:%s.\n", *jobid, - user_name[id]); + snprintf(tmp, 256, "The job %i belongs to user:%s not uid:%d.\n", + *jobid, user_name[id], client_uid); } } send_list_line(s, tmp); diff --git a/main.c b/main.c index 0ba5b99..dd7f4e6 100644 --- a/main.c +++ b/main.c @@ -462,6 +462,10 @@ static void print_help(const char *cmd) { "start.\n"); printf(" TS_USER_PATH path to the user configuration file, read on server " "starts.\n"); + printf(" TS_LOGFILE_PATH path to the job log file, read on server " + "starts\n"); + printf(" TS_JOBID The first job ID (default: 1000), read on server " + "starts.\n"); printf(" TMPDIR directory where to place the output files and the " "default socket.\n"); printf("Long option actions:\n"); diff --git a/main.h b/main.h index 0fa6794..15acca8 100644 --- a/main.h +++ b/main.h @@ -483,10 +483,14 @@ void read_user_file(const char *path); int get_user_id(int id); void c_refresh_user(); const char *get_user_path(); +void write_logfile(const struct Job *p); +int get_env_jobid(); /* jobs.c */ void s_user_status_all(int s); void s_user_status(int s, int i); void s_refresh_users(int s); +void set_jobids(int i); + /* client.c */ void c_list_jobs_all(); \ No newline at end of file diff --git a/server.c b/server.c index ee1c9c8..f926abe 100644 --- a/server.c +++ b/server.c @@ -205,6 +205,7 @@ void server_main(int notify_fd, char *_path) { user_queue[i] = 0; } + set_jobids(get_env_jobid()); read_user_file(get_user_path()); set_socket_model(_path); diff --git a/user.c b/user.c index 671c8fd..a638b8b 100644 --- a/user.c +++ b/user.c @@ -1,10 +1,14 @@ -#include "user.h" + #define _GNU_SOURCE #include #include #include +#include #include +#include "main.h" +#include "user.h" + void send_list_line(int s, const char *str); void error(const char *str, ...); @@ -18,6 +22,48 @@ const char *get_user_path() { } } +int get_env_jobid() { + char *str; + str = getenv("TS_JOBID"); + if (str == NULL || strlen(str) == 0) { + return 1000; + } else { + int i = atoi(str); + if (i < 0) + i = 1000; + return i; + } +} + +const char *get_server_logfile() { + char *str; + str = getenv("TS_LOGFILE_PATH"); + if (str == NULL || strlen(str) == 0) { + return "/home/kylin/task-spooler/log.txt"; + } else { + return str; + } +} + +void write_logfile(const struct Job *p) { + // char buf[1024] = ""; + FILE *f = fopen(get_server_logfile(), "a"); + if (f == NULL) { + return; + } + char buf[100]; + time_t now = time(0); + strftime(buf, 100, "%Y-%m-%d %H:%M:%S", localtime(&now)); + // snprintf(buf, 1024, "[%d] %s @ %s\n", p->jobid, p->command, date); + int user_id = p->user_id; + char *label = ".."; + if (p->label) + label = p->label; + fprintf(f, "[%d] %s P:%d <%s> Pid: %d CMD: %s @ %s\n", p->jobid, + user_name[user_id], p->num_slots, label, p->pid, p->command, buf); + fclose(f); +} + void read_user_file(const char *path) { server_uid = getuid(); if (server_uid != 0) { From 2ffbb0ac7e35abcc1b1cdfbf458b47f0bbd55ab1 Mon Sep 17 00:00:00 2001 From: kylin Date: Wed, 3 Aug 2022 13:46:39 +0800 Subject: [PATCH 10/91] version 0.1.4 --- client.c | 13 ++++++++++++- jobs.c | 1 + main.c | 13 ++++++++----- server.c | 6 ++++-- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/client.c b/client.c index 02f1bfd..8cdad3d 100644 --- a/client.c +++ b/client.c @@ -463,8 +463,19 @@ void c_kill_job() { void c_kill_all_jobs() { struct Msg m = default_msg(); + if (m.uid != 0) { + printf("Only the root can shutdown the ts server\n"); + return; + } int res; - + char buf[10]; + printf("Do you want to kill all jobs? (Yes/n) "); + scanf("%3s", buf); + if (strcmp(buf, "Yes") != 0) { + return; + } else { + printf("Kill all jobs!\n"); + } /* Send the request */ m.type = KILL_ALL; send_msg(server_socket, &m); diff --git a/jobs.c b/jobs.c index 81722e2..6ba925f 100644 --- a/jobs.c +++ b/jobs.c @@ -175,6 +175,7 @@ static void add_notify_errorlevel_to(struct Job *job, int jobid) { } void s_kill_all_jobs(int s) { + struct Job *p; s_count_running_jobs(s); diff --git a/main.c b/main.c index dd7f4e6..e847e85 100644 --- a/main.c +++ b/main.c @@ -491,12 +491,14 @@ static void print_help(const char *cmd) { "texts.\n"); printf("Actions:\n"); printf(" -A Show all users information\n"); - printf(" -X Refresh the user configuration\n"); - printf(" -K kill the task spooler server\n"); - printf(" -C clear the list of finished jobs\n"); + printf(" -X Refresh the user configuration (only available for " + "root)\n"); + printf(" -K kill the task spooler server (only available for " + "root)\n"); + printf(" -C clear the list of finished jobs for current user\n"); printf(" -l show the job list (default action)\n"); printf(" -S [num] get/set the number of max simultaneous jobs of the " - "server.\n"); + "server. (only available for root)\n"); printf(" -t [id] \"tail -n 10 -f\" the output of the job. Last run if " "not specified.\n"); printf(" -c [id] like -t, but shows all the lines. Last run if not " @@ -513,7 +515,8 @@ static void print_help(const char *cmd) { printf(" -w [id] wait for a job. The last added, if not specified.\n"); printf(" -k [id] send SIGTERM to the job process group. The last run, " "if not specified.\n"); - printf(" -T send SIGTERM to all running job groups.\n"); + printf(" -T send SIGTERM to all running job groups. (only " + "available for root)\n"); printf( " -u [id] put that job first. The last added, if not specified.\n"); printf(" -U swap two jobs in the queue.\n"); diff --git a/server.c b/server.c index f926abe..af2aa09 100644 --- a/server.c +++ b/server.c @@ -401,7 +401,8 @@ static enum Break client_read(int index) { s_process_runjob_ok(client_cs[index].jobid, buffer, m.u.output.pid); } break; case KILL_ALL: - s_kill_all_jobs(s); + if (m.uid == 0) + s_kill_all_jobs(s); break; case LIST: term_width = m.u.list.term_width; @@ -488,7 +489,8 @@ static enum Break client_read(int index) { s_move_urgent(s, m.u.jobid); break; case SET_MAX_SLOTS: - s_set_max_slots(m.u.max_slots); + if (m.uid == 0) + s_set_max_slots(m.u.max_slots); break; case GET_MAX_SLOTS: s_get_max_slots(s); From 4080d363551b885bb847e7b13a6e13a2f992c9c2 Mon Sep 17 00:00:00 2001 From: kylin Date: Wed, 10 Aug 2022 09:19:55 +0800 Subject: [PATCH 11/91] version 0.1.5 --- jobs.c | 13 ++++++++++++- main.c | 4 ++-- main.h | 2 ++ server.c | 14 ++++++++++++-- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/jobs.c b/jobs.c index 6ba925f..0beabfb 100644 --- a/jobs.c +++ b/jobs.c @@ -209,6 +209,15 @@ void s_count_running_jobs(int s) { send_msg(s, &m); } +int s_get_job_uid(int jobid) { + struct Job *p = get_job(jobid); + if (p == NULL) { + return -1; + } else { + return user_UID[p->user_id]; + } +} + void s_get_label(int s, int jobid) { struct Job *p = 0; char *label; @@ -973,7 +982,9 @@ void s_job_info(int s, int jobid) { } write(s, p->command, strlen(p->command)); fd_nprintf(s, 100, "\n"); + fd_nprintf(s, 100, "User: %s\n", user_name[p->user_id]); fd_nprintf(s, 100, "Slots required: %i\n", p->num_slots); + fd_nprintf(s, 100, "Output: %s\n", p->output_filename); fd_nprintf(s, 100, "Enqueue time: %s", ctime(&p->info.enqueue_time.tv_sec)); if (p->state == RUNNING) { fd_nprintf(s, 100, "Start time: %s", ctime(&p->info.start_time.tv_sec)); @@ -987,7 +998,7 @@ void s_job_info(int s, int jobid) { char *unit = time_rep(&t); fd_nprintf(s, 100, "Time run: %f%s\n", t, unit); } - fd_nprintf(s, 100, "extralines\n"); + fd_nprintf(s, 100, "\n"); } void s_send_last_id(int s) { diff --git a/main.c b/main.c index e847e85..a2c07a1 100644 --- a/main.c +++ b/main.c @@ -566,7 +566,7 @@ static void get_terminal_width() { int main(int argc, char **argv) { int errorlevel = 0; client_uid = getuid(); - printf("client_uid = %u\n", client_uid); + // printf("client_uid = %u\n", client_uid); init_version(); get_terminal_width(); @@ -611,7 +611,7 @@ int main(int argc, char **argv) { c_new_job(); command_line.jobid = c_wait_newjob_ok(); if (command_line.store_output) { - printf("%i\n", command_line.jobid); + printf("New JobID: %i\n", command_line.jobid); fflush(stdout); } if (command_line.should_go_background) { diff --git a/main.h b/main.h index 15acca8..fc1ce72 100644 --- a/main.h +++ b/main.h @@ -490,6 +490,8 @@ int get_env_jobid(); void s_user_status_all(int s); void s_user_status(int s, int i); void s_refresh_users(int s); +int s_get_job_uid(int jobid); + void set_jobids(int i); /* client.c */ diff --git a/server.c b/server.c index af2aa09..e4ffa28 100644 --- a/server.c +++ b/server.c @@ -486,7 +486,9 @@ static enum Break client_read(int index) { s_count_running_jobs(s); break; case URGENT: - s_move_urgent(s, m.u.jobid); + if (m.uid == 0 || m.uid == s_get_job_uid(m.u.jobid)) { + s_move_urgent(s, m.u.jobid); + } break; case SET_MAX_SLOTS: if (m.uid == 0) @@ -496,7 +498,15 @@ static enum Break client_read(int index) { s_get_max_slots(s); break; case SWAP_JOBS: - s_swap_jobs(s, m.u.swap.jobid1, m.u.swap.jobid2); + if (m.uid == 0) { + s_swap_jobs(s, m.u.swap.jobid1, m.u.swap.jobid2); + } else { + int job1_uid = s_get_job_uid(m.u.swap.jobid1); + int job2_uid = s_get_job_uid(m.u.swap.jobid2); + if (m.uid == job1_uid && m.uid == job2_uid) { + s_swap_jobs(s, m.u.swap.jobid1, m.u.swap.jobid2); + } + } break; case GET_STATE: s_send_state(s, m.u.jobid); From 34c7f495925e887f33eb6254bf0f9169f9848f73 Mon Sep 17 00:00:00 2001 From: kylin Date: Thu, 11 Aug 2022 17:58:53 +0800 Subject: [PATCH 12/91] version 0.1.6 --- client.c | 16 +++++++++++++- jobs.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ list.c | 35 ++++++++++++++++++++++++++--- main.c | 52 ++++++++++++++++++++++++++++++++++++++++++- main.h | 12 +++++++++- server.c | 31 ++++++++++++++++++++++++++ user.c | 17 +++++++++----- 7 files changed, 219 insertions(+), 11 deletions(-) diff --git a/client.c b/client.c index 8cdad3d..64cf01a 100644 --- a/client.c +++ b/client.c @@ -270,10 +270,24 @@ void c_show_info() { } void c_refresh_user() { + struct Msg m = default_msg(); + m.type = REFRESH_USERS; + send_msg(server_socket, &m); +} + +void c_stop_user(int uid) { struct Msg m = default_msg(); // int res; + m.type = STOP_USER; + m.u.jobid = uid; + send_msg(server_socket, &m); +} - m.type = REFRESH_USERS; +void c_cont_user(int uid) { + struct Msg m = default_msg(); + // int res; + m.type = CONT_USER; + m.u.jobid = uid; send_msg(server_socket, &m); } diff --git a/jobs.c b/jobs.c index 0beabfb..316b207 100644 --- a/jobs.c +++ b/jobs.c @@ -1014,6 +1014,73 @@ void s_refresh_users(int s) { send_list_line(s, "refresh the list success!\n"); } +void s_stop_all_users(int s) { + for (int i = 0; i < user_number; i++) { + s_stop_user(s, user_UID[i]); + } +} + +void s_cont_all_users(int s) { + for (int i = 0; i < user_number; i++) { + s_cont_user(s, user_UID[i]); + } +} + +void s_cont_user(int s, int uid) { + char buffer[256]; + // get the sequence of user_id + int user_id = get_user_id(uid); + if (user_id == -1) + return; + + user_max_slots[user_id] = abs(user_max_slots[user_id]); + + struct Job *p = firstjob; + while (p != NULL) { + if (p->user_id == user_id && p->state == RUNNING) { + // p->state = HOLDING_CLIENT; + if (p->pid != 0) { + kill(p->pid, SIGCONT); + } + } + p = p->next; + } + + snprintf(buffer, 256, "Resume user: [%d] %s\n", uid, user_name[user_id]); + send_list_line(s, buffer); +} + +void s_stop_user(int s, int uid) { + char buffer[256]; + // get the sequence of user_id + int user_id = get_user_id(uid); + if (user_id == -1) + return; + + user_max_slots[user_id] = -abs(user_max_slots[user_id]); + + struct Job *p = firstjob; + while (p != NULL) { + if (p->user_id == user_id && p->state == RUNNING) { + // p->state = HOLDING_CLIENT; + if (p->pid != 0) { + kill(p->pid, SIGSTOP); + } else { + char *label = "(...)"; + if (p->label != NULL) + label = p->label; + snprintf(buffer, 256, "Error in stop %s [%d] %s | %s\n", + user_name[user_id], p->jobid, label, p->command); + send_list_line(s, buffer); + } + } + p = p->next; + } + + snprintf(buffer, 256, "Lock user: [%d] %s\n", uid, user_name[user_id]); + send_list_line(s, buffer); +} + void s_send_output(int s, int jobid) { struct Job *p = 0; struct Msg m = default_msg(); diff --git a/list.c b/list.c index 36a61af..91babea 100644 --- a/list.c +++ b/list.c @@ -17,6 +17,31 @@ extern int busy_slots; extern int max_slots; +static int check_ifsleep(int pid) { + char filename[256]; + char name[256]; + char status = '\0'; + FILE *fp = NULL; + snprintf(filename, 256, "/proc/%d/stat", pid); + + fp = fopen(filename, "r"); + if (fp == NULL) { + fprintf(stderr, "Error: Couldn't open [%s]\n", filename); + return -1; + } + int token = fscanf(fp, "%d %s %c", &pid, name, &status); + if (token < 3) { + return -1; + } + fclose(fp); + + if (status == 'T') { + return 1; + } else { + return 0; + } +} + static char *shorten(char *line, int len) { char *newline = (char *)malloc((len + 1) * sizeof(char)); if (strlen(line) <= len) @@ -91,6 +116,9 @@ static char *print_noresult(const struct Job *p) { int cmd_len; jobstate = jstate2string(p->state); + if (p->pid != 0 && check_ifsleep(p->pid) == 1) { + jobstate = "holdon"; + } output_filename = ofilename_shown(p); char *uname = user_name[p->user_id]; @@ -123,7 +151,7 @@ static char *print_noresult(const struct Job *p) { struct timeval endtv; float real_ms; char *unit; - if (p->state == QUEUED) { + if (p->state == QUEUED || p->pid == 0) { real_ms = 0; unit = " "; } else { @@ -141,8 +169,9 @@ static char *print_noresult(const struct Job *p) { char *cmd = shorten(p->command, cmd_len); if (p->label) { char *label = shorten(p->label, 10); - snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-20s | %s\n", - p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, + snprintf(line, maxlen, + "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-20s %d | %s\n", p->jobid, + jobstate, p->num_slots, uname, label, real_ms, unit, cmd, p->pid, output_filename); free(label); free(cmd); diff --git a/main.c b/main.c index a2c07a1..b1bcef2 100644 --- a/main.c +++ b/main.c @@ -12,6 +12,7 @@ #define _TS_MAKE_STR(x) #x #include +#include #include #include #include @@ -122,7 +123,7 @@ int strtok_int(char *str, char *delim, int *ids) { } static struct option longOptions[] = { - {"get_label", optional_argument, NULL, 'a'}, + {"get_label", required_argument, NULL, 'a'}, {"count_running", no_argument, NULL, 'R'}, {"last_queue_id", no_argument, NULL, 'q'}, {"full_cmd", optional_argument, NULL, 'F'}, @@ -132,6 +133,8 @@ static struct option longOptions[] = { {"getenv", required_argument, NULL, 0}, {"setenv", required_argument, NULL, 0}, {"unsetenv", required_argument, NULL, 0}, + {"stop", optional_argument, NULL, 0}, + {"cont", optional_argument, NULL, 0}, {NULL, 0, NULL, 0}}; void parse_opts(int argc, char **argv) { @@ -155,6 +158,15 @@ void parse_opts(int argc, char **argv) { } else if (strcmp(longOptions[optionIdx].name, "set_logdir") == 0) { command_line.request = c_SET_LOGDIR; command_line.label = optarg; /* reuse this variable */ + } else if (strcmp(longOptions[optionIdx].name, "get_label") == 0) { + command_line.request = c_GET_LABEL; + command_line.jobid = atoi(optarg); + } else if (strcmp(longOptions[optionIdx].name, "stop") == 0) { + command_line.request = c_STOP_USER; + command_line.label = optarg; /* reuse this var */ + } else if (strcmp(longOptions[optionIdx].name, "cont") == 0) { + command_line.request = c_CONT_USER; + command_line.label = optarg; /* reuse this var */ } else if (strcmp(longOptions[optionIdx].name, "getenv") == 0) { command_line.request = c_GET_ENV; command_line.label = optarg; /* reuse this var */ @@ -596,6 +608,44 @@ int main(int argc, char **argv) { } break; + case c_STOP_USER: { + int stop_uid = client_uid; + if (command_line.label != NULL) { + if (client_uid == 0) { + struct passwd *pwd = getpwnam(command_line.label); + if (pwd == NULL) { + printf("Error: Cannot find user: %s to stop\n", command_line.label); + return -1; + } + stop_uid = pwd->pw_uid; + } else { + printf("Error: Cannot stop the user %s\n", command_line.label); + return -1; + } + } + printf("Stop user ID: %d\n", stop_uid); + c_stop_user(stop_uid); + c_wait_server_lines(); + } break; + case c_CONT_USER: { + int cont_uid = client_uid; + if (command_line.label != NULL) { + if (client_uid == 0) { + struct passwd *pwd = getpwnam(command_line.label); + if (pwd == NULL) { + printf("Error: Cannot find user: %s to resume\n", command_line.label); + return -1; + } + cont_uid = pwd->pw_uid; + } else { + printf("Error: Cannot resume the user %s\n", command_line.label); + return -1; + } + } + printf("Resume user ID: %d\n", cont_uid); + c_cont_user(cont_uid); + c_wait_server_lines(); + } break; case c_SHOW_VERSION: print_version(); break; diff --git a/main.h b/main.h index fc1ce72..dcebb81 100644 --- a/main.h +++ b/main.h @@ -17,6 +17,8 @@ enum MsgTypes { LIST_ALL, LIST_LINE, REFRESH_USERS, + STOP_USER, + CONT_USER, CLEAR_FINISHED, ASK_OUTPUT, ANSWER_OUTPUT, @@ -58,6 +60,8 @@ enum Request { c_LIST, c_LIST_ALL, c_REFRESH_USER, + c_STOP_USER, + c_CONT_USER, c_CLEAR_FINISHED, c_SHOW_HELP, c_SHOW_VERSION, @@ -491,8 +495,14 @@ void s_user_status_all(int s); void s_user_status(int s, int i); void s_refresh_users(int s); int s_get_job_uid(int jobid); +void s_stop_all_users(int s); +void s_stop_user(int s, int uid); +void s_cont_user(int s, int uid); +void s_cont_all_users(int s); void set_jobids(int i); /* client.c */ -void c_list_jobs_all(); \ No newline at end of file +void c_list_jobs_all(); +void c_stop_user(int uid); +void c_cont_user(int uid); diff --git a/server.c b/server.c index e4ffa28..8ac1720 100644 --- a/server.c +++ b/server.c @@ -371,6 +371,37 @@ static enum Break client_read(int index) { s_refresh_users(s); } close(s); + remove_connection(index); + break; + case STOP_USER: + if (m.uid == getuid()) { + if (m.u.jobid != 0) { + s_stop_user(s, m.u.jobid); + } else { + s_stop_all_users(s); + } + } else { + if (m.uid == m.u.jobid) + s_stop_user(s, m.u.jobid); + } + s_user_status_all(s); + close(s); + remove_connection(index); + break; + case CONT_USER: + if (m.uid == getuid()) { + if (m.u.jobid != 0) { + s_cont_user(s, m.u.jobid); + } else { + s_cont_all_users(s); + } + } else { + if (m.uid == m.u.jobid) + s_cont_user(s, m.u.jobid); + } + s_user_status_all(s); + close(s); + remove_connection(index); break; case KILL_SERVER: if (m.uid == getuid()) diff --git a/user.c b/user.c index a638b8b..1fe5caa 100644 --- a/user.c +++ b/user.c @@ -118,10 +118,13 @@ void read_user_file(const char *path) { void s_user_status_all(int s) { char buffer[256]; - send_list_line(s, "\n"); + char *extra; + send_list_line(s, "-- Users ----------- \n"); for (size_t i = 0; i < user_number; i++) { - snprintf(buffer, 256, "[%04d] %3d/%d %20s %d\n", user_UID[i], user_busy[i], - user_max_slots[i], user_name[i], user_jobs[user_number]); + extra = user_max_slots[i] < 0 ? "Locked" : ""; + snprintf(buffer, 256, "[%04d] %3d/%-4d %20s Run. %2d %s\n", user_UID[i], + user_busy[i], abs(user_max_slots[i]), user_name[i], user_jobs[i], + extra); send_list_line(s, buffer); } snprintf(buffer, 256, "Service at UID:%d\n", server_uid); @@ -130,8 +133,12 @@ void s_user_status_all(int s) { void s_user_status(int s, int i) { char buffer[256]; - snprintf(buffer, 256, "[%04d] %3d/%d %20s %d\n", user_UID[i], user_busy[i], - user_max_slots[i], user_name[i], user_jobs[user_number]); + char *extra = ""; + if (user_max_slots[i] < 0) + extra = "Locked"; + snprintf(buffer, 256, "[%04d] %3d/%-4d %20s Run. %2d %s\n", user_UID[i], + user_busy[i], abs(user_max_slots[i]), user_name[i], user_jobs[i], + extra); send_list_line(s, buffer); } From 87675d62c0b60e9d846787e21d2919f36413e534 Mon Sep 17 00:00:00 2001 From: kylin Date: Thu, 11 Aug 2022 19:32:01 +0800 Subject: [PATCH 13/91] version 0.1.7 add the user lock and unlock --- jobs.c | 70 ++++++++++++++++++++++++++++---------------------- list.c | 12 ++++++--- main.h | 2 ++ server.c | 20 +++++++++++---- server_start.c | 4 +++ user.c | 26 +++++++++++++------ user.h | 3 ++- 7 files changed, 89 insertions(+), 48 deletions(-) diff --git a/jobs.c b/jobs.c index 316b207..adb755c 100644 --- a/jobs.c +++ b/jobs.c @@ -671,8 +671,8 @@ void s_removejob(int jobid) { /* -1 if no one should be run. */ int next_run_job() { struct Job *p; - static int uid = 0; - uid = (uid + 1) % user_number; + // start from a random sequence + int uid = rand() % user_number; const int free_slots = max_slots - busy_slots; @@ -687,38 +687,44 @@ int next_run_job() { return -1; /* Look for a runnable task */ - p = firstjob; - while (p != 0) { - if (p->state == QUEUED) { - if (p->depend_on_size) { - int ready = 1; - for (int i = 0; i < p->depend_on_size; i++) { - struct Job *do_depend_job = get_job(p->depend_on[i]); - /* We won't try to run any job do_depending on an unfinished - * job */ - if (do_depend_job != NULL && (do_depend_job->state == QUEUED || - do_depend_job->state == RUNNING)) { - /* Next try */ - p = p->next; - ready = 0; - break; + for (int i = 0; i < user_number; i++) { + uid = (uid + 1) % user_number; + if (user_queue[uid] == 0) { + continue; + } + p = firstjob; + while (p != 0) { + if (p->state == QUEUED) { + if (p->depend_on_size) { + int ready = 1; + for (int i = 0; i < p->depend_on_size; i++) { + struct Job *do_depend_job = get_job(p->depend_on[i]); + /* We won't try to run any job do_depending on an unfinished + * job */ + if (do_depend_job != NULL && (do_depend_job->state == QUEUED || + do_depend_job->state == RUNNING)) { + /* Next try */ + p = p->next; + ready = 0; + break; + } } + if (ready != 1) + continue; } - if (ready != 1) - continue; - } - int num_slots = p->num_slots, id = p->user_id; - if (id == uid && free_slots >= num_slots && - user_max_slots[id] - user_busy[id] >= num_slots) { - busy_slots = busy_slots + num_slots; - user_busy[id] += num_slots; - user_jobs[id]++; - user_queue[id]--; - return p->jobid; + int num_slots = p->num_slots, id = p->user_id; + if (id == uid && free_slots >= num_slots && + user_max_slots[id] - user_busy[id] >= num_slots) { + busy_slots = busy_slots + num_slots; + user_busy[id] += num_slots; + user_jobs[id]++; + user_queue[id]--; + return p->jobid; + } } + p = p->next; } - p = p->next; } return -1; } @@ -1219,7 +1225,8 @@ int s_remove_job(int s, int *jobid, int client_uid) { else snprintf(tmp, 256, "The job %i cannot be removed.\n", *jobid); if (p == firstjob && p->state == RUNNING) { - kill(p->pid, SIGTERM); + if (p->pid != 0) + kill(p->pid, SIGTERM); snprintf(tmp, 256, "The first job %i is removed.\n", *jobid); } if (p != NULL) { @@ -1234,7 +1241,8 @@ int s_remove_job(int s, int *jobid, int client_uid) { } if (p->state == RUNNING) { - kill(p->pid, SIGTERM); + if (p->pid != 0) + kill(p->pid, SIGTERM); } /* if (p == firstjob) { diff --git a/list.c b/list.c index 91babea..04ef051 100644 --- a/list.c +++ b/list.c @@ -114,10 +114,16 @@ static char *print_noresult(const struct Job *p) { /* 20 chars should suffice for a string like "[int,int,..]&& " */ char dependstr[20] = ""; int cmd_len; - jobstate = jstate2string(p->state); - if (p->pid != 0 && check_ifsleep(p->pid) == 1) { - jobstate = "holdon"; + + if (p->state == RUNNING) { + if (p->pid == 0) { + jobstate = "N/A"; + } else { + if (check_ifsleep(p->pid) == 1) { + jobstate = "holdon"; + } + } } output_filename = ofilename_shown(p); diff --git a/main.h b/main.h index dcebb81..845d7f4 100644 --- a/main.h +++ b/main.h @@ -487,8 +487,10 @@ void read_user_file(const char *path); int get_user_id(int id); void c_refresh_user(); const char *get_user_path(); +const char *set_server_logfile(); void write_logfile(const struct Job *p); int get_env_jobid(); +void debug_write(const char *str); /* jobs.c */ void s_user_status_all(int s); diff --git a/server.c b/server.c index 8ac1720..7b4abc5 100644 --- a/server.c +++ b/server.c @@ -207,6 +207,7 @@ void server_main(int notify_fd, char *_path) { set_jobids(get_env_jobid()); read_user_file(get_user_path()); + set_server_logfile(); set_socket_model(_path); install_sigterm_handler(); @@ -216,7 +217,6 @@ void server_main(int notify_fd, char *_path) { initialize_log_dir(); notify_parent(notify_fd); - server_loop(ls); } @@ -244,14 +244,17 @@ static void server_loop(int ls) { FD_SET(ls, &readset); maxfd = ls; } + for (i = 0; i < nconnections; ++i) { FD_SET(client_cs[i].socket, &readset); if (client_cs[i].socket > maxfd) maxfd = client_cs[i].socket; } + select(maxfd + 1, &readset, NULL, NULL, NULL); if (FD_ISSET(ls, &readset)) { int cs; + // wait the connection cs = accept(ls, NULL, NULL); if (cs == -1) error("Accepting from %i", ls); @@ -259,6 +262,7 @@ static void server_loop(int ls) { client_cs[nconnections].socket = cs; ++nconnections; } + for (i = 0; i < nconnections; ++i) if (FD_ISSET(client_cs[i].socket, &readset)) { enum Break b; @@ -377,14 +381,17 @@ static enum Break client_read(int index) { if (m.uid == getuid()) { if (m.u.jobid != 0) { s_stop_user(s, m.u.jobid); + s_user_status_all(s); } else { s_stop_all_users(s); + s_user_status(s, get_user_id(m.u.jobid)); } } else { - if (m.uid == m.u.jobid) + if (m.uid == m.u.jobid) { s_stop_user(s, m.u.jobid); + s_user_status(s, get_user_id(m.u.jobid)); + } } - s_user_status_all(s); close(s); remove_connection(index); break; @@ -392,14 +399,17 @@ static enum Break client_read(int index) { if (m.uid == getuid()) { if (m.u.jobid != 0) { s_cont_user(s, m.u.jobid); + s_user_status_all(s); } else { s_cont_all_users(s); + s_user_status(s, get_user_id(m.u.jobid)); } } else { - if (m.uid == m.u.jobid) + if (m.uid == m.u.jobid) { s_cont_user(s, m.u.jobid); + s_user_status(s, get_user_id(m.u.jobid)); + } } - s_user_status_all(s); close(s); remove_connection(index); break; diff --git a/server_start.c b/server_start.c index 1623db6..565dc2d 100644 --- a/server_start.c +++ b/server_start.c @@ -123,6 +123,10 @@ static int fork_server() { case -1: /* Error */ return -1; default: /* Parent */ + printf("Start tast-spooler server from root[%d]\n", client_uid); + printf(" Read user file from %s [TS_USER_PATH]\n", get_user_path()); + printf(" Write log file to %s [TS_LOGFILE_PATH]\n\n", + set_server_logfile()); close(p[1]); } /* Return the read fd */ diff --git a/user.c b/user.c index 1fe5caa..fc05a59 100644 --- a/user.c +++ b/user.c @@ -35,19 +35,17 @@ int get_env_jobid() { } } -const char *get_server_logfile() { - char *str; - str = getenv("TS_LOGFILE_PATH"); - if (str == NULL || strlen(str) == 0) { - return "/home/kylin/task-spooler/log.txt"; - } else { - return str; +const char *set_server_logfile() { + logfile_path = getenv("TS_LOGFILE_PATH"); + if (logfile_path == NULL || strlen(logfile_path) == 0) { + logfile_path = "/home/kylin/task-spooler/log.txt"; } + return logfile_path; } void write_logfile(const struct Job *p) { // char buf[1024] = ""; - FILE *f = fopen(get_server_logfile(), "a"); + FILE *f = fopen(logfile_path, "a"); if (f == NULL) { return; } @@ -64,6 +62,18 @@ void write_logfile(const struct Job *p) { fclose(f); } +void debug_write(const char *str) { + FILE *f = fopen(logfile_path, "a"); + if (f == NULL) { + return; + } + char buf[100]; + time_t now = time(0); + strftime(buf, 100, "%Y-%m-%d %H:%M:%S", localtime(&now)); + fprintf(f, "%s @ %s\n", buf, str); + fclose(f); +} + void read_user_file(const char *path) { server_uid = getuid(); if (server_uid != 0) { diff --git a/user.h b/user.h index bb83fb3..135da9c 100644 --- a/user.h +++ b/user.h @@ -8,4 +8,5 @@ int user_UID[USER_MAX]; int user_busy[USER_MAX]; int user_jobs[USER_MAX]; int user_queue[USER_MAX]; -int user_number; \ No newline at end of file +int user_number; +char *logfile_path; \ No newline at end of file From 224f7ef9d01c23910792019a783b90524e68b272 Mon Sep 17 00:00:00 2001 From: kylin Date: Thu, 11 Aug 2022 20:32:49 +0800 Subject: [PATCH 14/91] version 0.1.7 add task pause and resume --- client.c | 26 +++++++++++++++++++ jobs.c | 42 +++++++++++++++++++++++++++++++ main.c | 77 ++++++++++++++++++++++++++++++++++++++++++++------------ main.h | 16 ++++++++++++ server.c | 10 ++++++++ user.c | 10 ++++++++ 6 files changed, 165 insertions(+), 16 deletions(-) diff --git a/client.c b/client.c index 64cf01a..3dc7c2b 100644 --- a/client.c +++ b/client.c @@ -275,6 +275,32 @@ void c_refresh_user() { send_msg(server_socket, &m); } +void c_lock_server() { + struct Msg m = default_msg(); + m.type = LOCK_SERVER; + send_msg(server_socket, &m); +} + +void c_unlock_server() { + struct Msg m = default_msg(); + m.type = UNLOCK_SERVER; + send_msg(server_socket, &m); +} + +void c_hold_job(int jobid) { + struct Msg m = default_msg(); + m.type = HOLD_JOB; + m.u.jobid = jobid; + send_msg(server_socket, &m); +} + +void c_restart_job(int jobid) { + struct Msg m = default_msg(); + m.type = RESTART_JOB; + m.u.jobid = jobid; + send_msg(server_socket, &m); +} + void c_stop_user(int uid) { struct Msg m = default_msg(); // int res; diff --git a/jobs.c b/jobs.c index adb755c..c0fcbea 100644 --- a/jobs.c +++ b/jobs.c @@ -1322,6 +1322,48 @@ static struct Job *get_job(int jobid) { return 0; } +void s_hold_job(int s, int jobid, int uid) { + struct Job *p; + char buff[256]; + p = findjob(jobid); + if (p == 0) { + snprintf(buff, 256, "Error: cannot find job [%d]\n", jobid); + send_list_line(s, buff); + return; + } + + int job_UID = user_UID[p->user_id]; + if (p->pid != 0 && (job_UID = uid || uid == 0)) { + kill(p->pid, SIGSTOP); + snprintf(buff, 256, "Hold on job [%d] successfully!\n", jobid); + + } else { + snprintf(buff, 256, "Error: cannot hold on job [%d]\n", jobid); + } + send_list_line(s, buff); +} + +void s_restart_job(int s, int jobid, int uid) { + struct Job *p; + char buff[256]; + + p = findjob(jobid); + if (p == 0) { + snprintf(buff, 256, "Error: cannot find job [%d]\n", jobid); + send_list_line(s, buff); + + return; + } + + int job_UID = user_UID[p->user_id]; + if (p->pid != 0 && (job_UID = uid || uid == 0)) { + kill(p->pid, SIGCONT); + snprintf(buff, 256, "Restart job [%d] successfully!\n", jobid); + } else { + snprintf(buff, 256, "Error: cannot hold on job [%d]\n", jobid); + } + send_list_line(s, buff); +} /* Don't complain, if the socket doesn't exist */ void s_remove_notification(int s) { struct Notify *n; diff --git a/main.c b/main.c index b1bcef2..cf4b31f 100644 --- a/main.c +++ b/main.c @@ -125,6 +125,7 @@ int strtok_int(char *str, char *delim, int *ids) { static struct option longOptions[] = { {"get_label", required_argument, NULL, 'a'}, {"count_running", no_argument, NULL, 'R'}, + {"help", no_argument, NULL, 0}, {"last_queue_id", no_argument, NULL, 'q'}, {"full_cmd", optional_argument, NULL, 'F'}, {"plain", no_argument, NULL, 0}, @@ -135,6 +136,10 @@ static struct option longOptions[] = { {"unsetenv", required_argument, NULL, 0}, {"stop", optional_argument, NULL, 0}, {"cont", optional_argument, NULL, 0}, + {"hold", required_argument, NULL, 0}, + {"restart", required_argument, NULL, 0}, + {"lock-ts", no_argument, NULL, 0}, + {"unlock-ts", no_argument, NULL, 0}, {NULL, 0, NULL, 0}}; void parse_opts(int argc, char **argv) { @@ -158,9 +163,21 @@ void parse_opts(int argc, char **argv) { } else if (strcmp(longOptions[optionIdx].name, "set_logdir") == 0) { command_line.request = c_SET_LOGDIR; command_line.label = optarg; /* reuse this variable */ + } else if (strcmp(longOptions[optionIdx].name, "help") == 0) { + command_line.request = c_SHOW_HELP; } else if (strcmp(longOptions[optionIdx].name, "get_label") == 0) { command_line.request = c_GET_LABEL; - command_line.jobid = atoi(optarg); + command_line.jobid = str2int(optarg); + } else if (strcmp(longOptions[optionIdx].name, "hold") == 0) { + command_line.request = c_HOLD_JOB; + command_line.jobid = str2int(optarg); + } else if (strcmp(longOptions[optionIdx].name, "restart") == 0) { + command_line.request = c_RESTART_JOB; + command_line.jobid = str2int(optarg); + } else if (strcmp(longOptions[optionIdx].name, "lock-ts") == 0) { + command_line.request = c_LOCK_SERVER; + } else if (strcmp(longOptions[optionIdx].name, "unlock-ts") == 0) { + command_line.request = c_UNLOCK_SERVER; } else if (strcmp(longOptions[optionIdx].name, "stop") == 0) { command_line.request = c_STOP_USER; command_line.label = optarg; /* reuse this var */ @@ -194,7 +211,7 @@ void parse_opts(int argc, char **argv) { break; case 'k': command_line.request = c_KILL_JOB; - command_line.jobid = atoi(optarg); + command_line.jobid = str2int(optarg); break; case 'l': command_line.request = c_LIST; @@ -218,11 +235,11 @@ void parse_opts(int argc, char **argv) { break; case 'c': command_line.request = c_CAT; - command_line.jobid = atoi(optarg); + command_line.jobid = str2int(optarg); break; case 'o': command_line.request = c_SHOW_OUTPUT_FILE; - command_line.jobid = atoi(optarg); + command_line.jobid = str2int(optarg); break; case 'O': command_line.logfile = optarg; @@ -244,51 +261,51 @@ void parse_opts(int argc, char **argv) { break; case 't': command_line.request = c_TAIL; - command_line.jobid = atoi(optarg); + command_line.jobid = str2int(optarg); break; case 'p': command_line.request = c_SHOW_PID; - command_line.jobid = atoi(optarg); + command_line.jobid = str2int(optarg); break; case 'i': command_line.request = c_INFO; - command_line.jobid = atoi(optarg); + command_line.jobid = str2int(optarg); break; case 'q': command_line.request = c_LAST_ID; break; case 'a': command_line.request = c_GET_LABEL; - command_line.jobid = atoi(optarg); + command_line.jobid = str2int(optarg); break; case 'F': command_line.request = c_SHOW_CMD; - command_line.jobid = atoi(optarg); + command_line.jobid = str2int(optarg); break; case 'N': - command_line.num_slots = atoi(optarg); + command_line.num_slots = str2int(optarg); if (command_line.num_slots < 0) command_line.num_slots = 0; break; case 'r': command_line.request = c_REMOVEJOB; - command_line.jobid = atoi(optarg); + command_line.jobid = str2int(optarg); break; case 'w': command_line.request = c_WAITJOB; - command_line.jobid = atoi(optarg); + command_line.jobid = str2int(optarg); break; case 'u': command_line.request = c_URGENT; - command_line.jobid = atoi(optarg); + command_line.jobid = str2int(optarg); break; case 's': command_line.request = c_GET_STATE; - command_line.jobid = atoi(optarg); + command_line.jobid = str2int(optarg); break; case 'S': command_line.request = c_SET_MAX_SLOTS; - command_line.max_slots = atoi(optarg); + command_line.max_slots = str2int(optarg); if (command_line.max_slots < 1) { fprintf(stderr, "You should set at minimum 1 slot.\n"); exit(-1); @@ -501,6 +518,15 @@ static void print_help(const char *cmd) { " --set_logdir [path] set the path containing log files.\n"); printf(" --plain list jobs in plain tab-separated " "texts.\n"); + printf(" --hold_job [jobid] hold on a task.\n"); + printf(" --restart_job [jobid] restart a task.\n"); + printf(" --stop [user] For normal user, pause all " + "tasks and lock the account. \n " + " For root, to lock all users or single [user].\n"); + printf(" --cont [user] For normal user, continue all " + "paused tasks and lock the account. \n " + " For root, to unlock all users or single [user].\n"); + printf("Actions:\n"); printf(" -A Show all users information\n"); printf(" -X Refresh the user configuration (only available for " @@ -532,7 +558,7 @@ static void print_help(const char *cmd) { printf( " -u [id] put that job first. The last added, if not specified.\n"); printf(" -U swap two jobs in the queue.\n"); - printf(" -h show this help\n"); + printf(" -h | --help show this help\n"); printf(" -V show the program version\n"); printf("Options adding jobs:\n"); printf(" -B in case of full clients on the server, quit instead " @@ -577,6 +603,7 @@ static void get_terminal_width() { int main(int argc, char **argv) { int errorlevel = 0; + usr_locker = -1; client_uid = getuid(); // printf("client_uid = %u\n", client_uid); @@ -607,6 +634,24 @@ int main(int argc, char **argv) { printf("Only the root can shutdown the task-spooler server\n"); } + break; + case c_HOLD_JOB: + c_hold_job(command_line.jobid); + c_wait_server_lines(); + + break; + case c_RESTART_JOB: + c_restart_job(command_line.jobid); + c_wait_server_lines(); + + break; + case c_LOCK_SERVER: + c_lock_server(); + c_wait_server_lines(); + break; + case c_UNLOCK_SERVER: + c_unlock_server(); + c_wait_server_lines(); break; case c_STOP_USER: { int stop_uid = client_uid; diff --git a/main.h b/main.h index 845d7f4..ec721b7 100644 --- a/main.h +++ b/main.h @@ -17,6 +17,10 @@ enum MsgTypes { LIST_ALL, LIST_LINE, REFRESH_USERS, + HOLD_JOB, + RESTART_JOB, + LOCK_SERVER, + UNLOCK_SERVER, STOP_USER, CONT_USER, CLEAR_FINISHED, @@ -62,6 +66,10 @@ enum Request { c_REFRESH_USER, c_STOP_USER, c_CONT_USER, + c_LOCK_SERVER, + c_UNLOCK_SERVER, + c_HOLD_JOB, + c_RESTART_JOB, c_CLEAR_FINISHED, c_SHOW_HELP, c_SHOW_VERSION, @@ -490,7 +498,9 @@ const char *get_user_path(); const char *set_server_logfile(); void write_logfile(const struct Job *p); int get_env_jobid(); +long str2int(const char *str); void debug_write(const char *str); +int usr_locker; /* jobs.c */ void s_user_status_all(int s); @@ -501,6 +511,8 @@ void s_stop_all_users(int s); void s_stop_user(int s, int uid); void s_cont_user(int s, int uid); void s_cont_all_users(int s); +void s_hold_job(int s, int jobid, int uid); +void s_restart_job(int s, int jobid, int uid); void set_jobids(int i); @@ -508,3 +520,7 @@ void set_jobids(int i); void c_list_jobs_all(); void c_stop_user(int uid); void c_cont_user(int uid); +void c_hold_job(int jobid); +void c_lock_server(); +void c_unlock_server(); +void c_restart_job(int jobid); diff --git a/server.c b/server.c index 7b4abc5..c4286ac 100644 --- a/server.c +++ b/server.c @@ -377,6 +377,16 @@ static enum Break client_read(int index) { close(s); remove_connection(index); break; + case HOLD_JOB: + s_hold_job(s, m.u.jobid, m.uid); + close(s); + remove_connection(index); + break; + case RESTART_JOB: + s_restart_job(s, m.u.jobid, m.uid); + close(s); + remove_connection(index); + break; case STOP_USER: if (m.uid == getuid()) { if (m.u.jobid != 0) { diff --git a/user.c b/user.c index fc05a59..9b8cf16 100644 --- a/user.c +++ b/user.c @@ -1,5 +1,6 @@ #define _GNU_SOURCE +#include #include #include #include @@ -35,6 +36,15 @@ int get_env_jobid() { } } +long str2int(const char *str) { + long i; + if (sscanf(str, "%ld", &i) == 0) { + printf("Error in convert %s to number\n", str); + exit(-1); + } + return i; +} + const char *set_server_logfile() { logfile_path = getenv("TS_LOGFILE_PATH"); if (logfile_path == NULL || strlen(logfile_path) == 0) { From 8c3f3a9e5d30d2349ea06e51fd577c853035d104 Mon Sep 17 00:00:00 2001 From: kylin Date: Thu, 11 Aug 2022 21:50:10 +0800 Subject: [PATCH 15/91] version 0.1.8 add the server locker --- client.c | 18 +++++++-- jobs.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++--------- list.c | 15 ++++++-- main.c | 12 +++--- main.h | 16 +++++--- server.c | 13 +++++++ user.c | 11 ++++++ 7 files changed, 163 insertions(+), 36 deletions(-) diff --git a/client.c b/client.c index 3dc7c2b..29bb1fc 100644 --- a/client.c +++ b/client.c @@ -155,10 +155,10 @@ int c_wait_server_commands() { return -1; } -void c_wait_server_lines() { +static int wait_server_lines_and_check(const char *pre_str) { struct Msg m = default_msg(); int res; - + int error_sig = 0; while (1) { res = recv_msg(server_socket, &m); if (res == -1) @@ -172,12 +172,18 @@ void c_wait_server_lines() { char *buffer; buffer = (char *)malloc(m.u.size); recv_bytes(server_socket, buffer, m.u.size); + if (strncmp(buffer, pre_str, strlen(pre_str)) == 0) { + error_sig++; + } printf("%s", buffer); free(buffer); } } + return error_sig; } +void c_wait_server_lines() { wait_server_lines_and_check(""); } + void c_list_jobs() { struct Msg m = default_msg(); @@ -275,16 +281,20 @@ void c_refresh_user() { send_msg(server_socket, &m); } -void c_lock_server() { +int c_lock_server() { struct Msg m = default_msg(); m.type = LOCK_SERVER; send_msg(server_socket, &m); + int error_sig = wait_server_lines_and_check("Error:"); + return error_sig; } -void c_unlock_server() { +int c_unlock_server() { struct Msg m = default_msg(); m.type = UNLOCK_SERVER; send_msg(server_socket, &m); + int error_sig = wait_server_lines_and_check("Error:"); + return error_sig; } void c_hold_job(int jobid) { diff --git a/jobs.c b/jobs.c index c0fcbea..b83f78d 100644 --- a/jobs.c +++ b/jobs.c @@ -40,7 +40,7 @@ static int last_errorlevel = 0; /* Before the first job, let's consider static int last_finished_jobid; static struct Notify *first_notify = 0; - +static char buff[256]; /* server will access them */ int max_jobs; @@ -1033,7 +1033,6 @@ void s_cont_all_users(int s) { } void s_cont_user(int s, int uid) { - char buffer[256]; // get the sequence of user_id int user_id = get_user_id(uid); if (user_id == -1) @@ -1052,12 +1051,11 @@ void s_cont_user(int s, int uid) { p = p->next; } - snprintf(buffer, 256, "Resume user: [%d] %s\n", uid, user_name[user_id]); - send_list_line(s, buffer); + snprintf(buff, 256, "Resume user: [%d] %s\n", uid, user_name[user_id]); + send_list_line(s, buff); } void s_stop_user(int s, int uid) { - char buffer[256]; // get the sequence of user_id int user_id = get_user_id(uid); if (user_id == -1) @@ -1075,16 +1073,16 @@ void s_stop_user(int s, int uid) { char *label = "(...)"; if (p->label != NULL) label = p->label; - snprintf(buffer, 256, "Error in stop %s [%d] %s | %s\n", + snprintf(buff, 256, "Error in stop %s [%d] %s | %s\n", user_name[user_id], p->jobid, label, p->command); - send_list_line(s, buffer); + send_list_line(s, buff); } } p = p->next; } - snprintf(buffer, 256, "Lock user: [%d] %s\n", uid, user_name[user_id]); - send_list_line(s, buffer); + snprintf(buff, 256, "Lock user: [%d] %s\n", uid, user_name[user_id]); + send_list_line(s, buff); } void s_send_output(int s, int jobid) { @@ -1218,25 +1216,23 @@ int s_remove_job(int s, int *jobid, int client_uid) { if (p == NULL || p == firstjob || (user_UID[p->user_id] != client_uid)) { - char tmp[256]; - if (*jobid == -1) - snprintf(tmp, 256, "The last job cannot be removed.\n"); + snprintf(buff, 256, "The last job cannot be removed.\n"); else - snprintf(tmp, 256, "The job %i cannot be removed.\n", *jobid); + snprintf(buff, 256, "The job %i cannot be removed.\n", *jobid); if (p == firstjob && p->state == RUNNING) { if (p->pid != 0) kill(p->pid, SIGTERM); - snprintf(tmp, 256, "The first job %i is removed.\n", *jobid); + snprintf(buff, 256, "The first job %i is removed.\n", *jobid); } if (p != NULL) { int id = p->user_id; if (user_UID[id] != client_uid) { - snprintf(tmp, 256, "The job %i belongs to user:%s not uid:%d.\n", + snprintf(buff, 256, "The job %i belongs to user:%s not uid:%d.\n", *jobid, user_name[id], client_uid); } } - send_list_line(s, tmp); + send_list_line(s, buff); return 0; } @@ -1322,9 +1318,92 @@ static struct Job *get_job(int jobid) { return 0; } +int s_check_locker(int s, int uid) { + int dt = time(NULL) - locker_time; + int res; + + if (user_locker != 0 && dt > 30) { + user_locker = -1; + } + + if (user_locker == -1) { + res = 0; + } else { + if (user_locker == uid) { + res = 0; + } else { + res = 1; + } + } + + return res; +} + +void s_lock_server(int s, int uid) { + if (uid == 0) { + user_locker = 0; + locker_time = time(NULL); + snprintf(buff, 256, "lock the task-spooler server by Root\n"); + } else { + if (user_locker == -1) { + int user_id = get_user_id(uid); + if (user_id != -1) { + user_locker = uid; + locker_time = time(NULL); + snprintf(buff, 256, "lock the task-spooler server by `%s`\n", + user_name[user_id]); + } else { + snprintf(buff, 256, + "Error: cannot lock the task-spooler server by UID: [%d]\n", + uid); + } + } else { + if (user_locker == uid) { + snprintf(buff, 256, + "The task-spooler server has already been locked by `%s`\n", + uid2user_name(uid)); + } else { + snprintf(buff, 256, + "Error: the task-spooler server cannot be locked by `%s`\n", + uid2user_name(uid)); + } + } + } + send_list_line(s, buff); +} + +void s_unlock_server(int s, int uid) { + if (uid == 0) { + user_locker = -1; + snprintf(buff, 256, "Unlock the task-spooler server by Root\n"); + } else { + if (user_locker == uid) { + int user_id = get_user_id(uid); + if (user_id != -1) { + user_locker = -1; + snprintf(buff, 256, "Unlock the task-spooler server by `%s`\n", + user_name[user_id]); + } else { + snprintf(buff, 256, + "Error: cannot lock the task-spooler server by UID: [%d]\n", + uid); + } + } else { + if (user_locker == -1) { + snprintf(buff, 256, + "The task-spooler server has already been unlocked\n"); + } else { + snprintf(buff, 256, + "Error: the task-spooler server cannot be unlocked by `%s`\n", + uid2user_name(uid)); + } + } + } + send_list_line(s, buff); +} + void s_hold_job(int s, int jobid, int uid) { struct Job *p; - char buff[256]; p = findjob(jobid); if (p == 0) { snprintf(buff, 256, "Error: cannot find job [%d]\n", jobid); @@ -1345,7 +1424,6 @@ void s_hold_job(int s, int jobid, int uid) { void s_restart_job(int s, int jobid, int uid) { struct Job *p; - char buff[256]; p = findjob(jobid); if (p == 0) { diff --git a/list.c b/list.c index 04ef051..318929c 100644 --- a/list.c +++ b/list.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "main.h" #include "user.h" @@ -71,12 +72,18 @@ char *joblistdump_headers() { char *joblist_headers() { char *line; + char extra[100] = ""; + if (user_locker != -1) { + time_t dt = time(NULL) - locker_time; + snprintf(extra, 100, "Locked by `%s` for %ld sec.", + uid2user_name(user_locker), dt); + } - line = malloc(100); - snprintf(line, 100, - "%-4s %-7s %-7s %-6s %-10s %6s %-20s %s [run=%i/%i %.2f%%]\n", + line = malloc(256); + snprintf(line, 256, + "%-4s %-7s %-7s %-6s %-10s %6s %-20s %s [run=%i/%i %.2f%%] %s\n", "ID", "State", "Proc.", "User", "Label", "Time", "Command", "Log", - busy_slots, max_slots, 100.0 * busy_slots / max_slots); + busy_slots, max_slots, 100.0 * busy_slots / max_slots, extra); return line; } diff --git a/main.c b/main.c index cf4b31f..6d8c3bc 100644 --- a/main.c +++ b/main.c @@ -520,6 +520,10 @@ static void print_help(const char *cmd) { "texts.\n"); printf(" --hold_job [jobid] hold on a task.\n"); printf(" --restart_job [jobid] restart a task.\n"); + printf(" --lock Locker the server (Timeout: 30 " + "sec.)git " + "For Root, timeout is infinity.\n"); + printf(" --unlock Unlocker the server.\n"); printf(" --stop [user] For normal user, pause all " "tasks and lock the account. \n " " For root, to lock all users or single [user].\n"); @@ -603,7 +607,7 @@ static void get_terminal_width() { int main(int argc, char **argv) { int errorlevel = 0; - usr_locker = -1; + user_locker = -1; client_uid = getuid(); // printf("client_uid = %u\n", client_uid); @@ -646,12 +650,10 @@ int main(int argc, char **argv) { break; case c_LOCK_SERVER: - c_lock_server(); - c_wait_server_lines(); + errorlevel = c_lock_server(); break; case c_UNLOCK_SERVER: - c_unlock_server(); - c_wait_server_lines(); + errorlevel = c_unlock_server(); break; case c_STOP_USER: { int stop_uid = client_uid; diff --git a/main.h b/main.h index ec721b7..a13f225 100644 --- a/main.h +++ b/main.h @@ -492,7 +492,7 @@ int tail_file(const char *fname, int last_lines); /* user.c */ void read_user_file(const char *path); -int get_user_id(int id); +int get_user_id(int uid); void c_refresh_user(); const char *get_user_path(); const char *set_server_logfile(); @@ -500,7 +500,11 @@ void write_logfile(const struct Job *p); int get_env_jobid(); long str2int(const char *str); void debug_write(const char *str); -int usr_locker; +const char *uid2user_name(int uid); + +/* locker */ +int user_locker; +time_t locker_time; /* jobs.c */ void s_user_status_all(int s); @@ -513,7 +517,9 @@ void s_cont_user(int s, int uid); void s_cont_all_users(int s); void s_hold_job(int s, int jobid, int uid); void s_restart_job(int s, int jobid, int uid); - +void s_lock_server(int s, int uid); +void s_unlock_server(int s, int uid); +int s_check_locker(int s, int uid); void set_jobids(int i); /* client.c */ @@ -521,6 +527,6 @@ void c_list_jobs_all(); void c_stop_user(int uid); void c_cont_user(int uid); void c_hold_job(int jobid); -void c_lock_server(); -void c_unlock_server(); +int c_lock_server(); +int c_unlock_server(); void c_restart_job(int jobid); diff --git a/server.c b/server.c index c4286ac..581a377 100644 --- a/server.c +++ b/server.c @@ -377,6 +377,16 @@ static enum Break client_read(int index) { close(s); remove_connection(index); break; + case LOCK_SERVER: + s_lock_server(s, m.uid); + close(s); + remove_connection(index); + break; + case UNLOCK_SERVER: + s_unlock_server(s, m.uid); + close(s); + remove_connection(index); + break; case HOLD_JOB: s_hold_job(s, m.u.jobid, m.uid); close(s); @@ -431,6 +441,9 @@ static enum Break client_read(int index) { if (user_id == -1) { break; } + if (s_check_locker(s, m.uid)) { + break; + } client_cs[index].jobid = s_newjob(s, &m); client_cs[index].hasjob = 1; if (!job_is_holding_client(client_cs[index].jobid)) diff --git a/user.c b/user.c index 9b8cf16..8df2819 100644 --- a/user.c +++ b/user.c @@ -136,6 +136,17 @@ void read_user_file(const char *path) { free(line); } +const char *uid2user_name(int uid) { + if (uid == 0) + return "Root"; + int user_id = get_user_id(uid); + if (user_id != -1) { + return user_name[user_id]; + } else { + return "Unknown"; + } +} + void s_user_status_all(int s) { char buffer[256]; char *extra; From 43c56b6cdb09a2fd65b8bb074abc42efa2fa222f Mon Sep 17 00:00:00 2001 From: kylin Date: Thu, 11 Aug 2022 21:52:43 +0800 Subject: [PATCH 16/91] version 0.1.8 add the server locker --- README.md | 68 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 025ed00..32642db 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Task Spooler -Originally, [Task Spooler by Lluís Batlle i Rossell](https://vicerveza.homeunix.net/~viric/soft/ts/). +Originally, [Task Spooler by Lluís Batlle i Rossell](https://vicerveza.homeunix.net/~viric/soft/ts/). I focked the task spooler and add the function to run is with multiple users. ## Introduction @@ -125,7 +125,6 @@ Duc Nguyen took the project and develops a GPU-support version. See below or `man ts` for more details. ``` -usage: ts [action] [-ngfmdE] [-L ] [-D ] [cmd...] Env vars: TS_SOCKET the path to the unix socket used by the ts command. TS_MAILTO where to mail the result (on -m). Local user by default. @@ -135,39 +134,52 @@ Env vars: TS_ENV command called on enqueue. Its output determines the job information. TS_SAVELIST filename which will store the list, if the server dies. TS_SLOTS amount of jobs which can run at once, read on server start. + TS_USER_PATH path to the user configuration file, read on server starts. + TS_LOGFILE_PATH path to the job log file, read on server starts + TS_JOBID The first job ID (default: 1000), read on server starts. TMPDIR directory where to place the output files and the default socket. Long option actions: - --getenv get the value of the specified variable in server environment. - --setenv set the specified flag to server environment. - --unsetenv remove the specified flag from server environment. + --getenv [var] get the value of the specified variable in server environment. + --setenv [var] set the specified flag to server environment. + --unsetenv [var] remove the specified flag from server environment. --get_label || -a [id] show the job label. Of the last added, if not specified. --full_cmd || -F [id] show full command. Of the last added, if not specified. --count_running || -R return the number of running jobs --last_queue_id || -q show the job ID of the last added. --get_logdir get the path containing log files. - --set_logdir set the path containing log files. - --plain list jobs in plain tab-separated texts. + --set_logdir [path] set the path containing log files. + --plain list jobs in plain tab-separated texts. + --hold_job [jobid] hold on a task. + --restart_job [jobid] restart a task. + --lock Locker the server (Timeout: 30 sec.). For Root, timeout is infinity. + --unlock Unlocker the server. + --stop [user] For normal user, pause all tasks and lock the account. + For root, to lock all users or single [user]. + --cont [user] For normal user, continue all paused tasks and lock the account. + For root, to unlock all users or single [user]. Actions: - -K kill the task spooler server - -C clear the list of finished jobs - -l show the job list (default action) - -S [num] get/set the number of max simultaneous jobs of the server. - -t [id] \"tail -n 10 -f\" the output of the job. Last run if not specified. - -c [id] like -t, but shows all the lines. Last run if not specified. - -p [id] show the pid of the job. Last run if not specified. - -o [id] show the output file. Of last job run, if not specified. - -i [id] show job information. Of last job run, if not specified. - -s [id] show the job state. Of the last added, if not specified. - -r [id] remove a job. The last added, if not specified. - -w [id] wait for a job. The last added, if not specified. - -k [id] send SIGTERM to the job process group. The last run, if not specified. - -T send SIGTERM to all running job groups. - -u [id] put that job first. The last added, if not specified. - -U swap two jobs in the queue. - -B in case of full queue on the server, quit (2) instead of waiting. - -h show this help - -V show the program version + -A Show all users information + -X Refresh the user configuration (only available for root) + -K kill the task spooler server (only available for root) + -C clear the list of finished jobs for current user + -l show the job list (default action) + -S [num] get/set the number of max simultaneous jobs of the server. (only available for root) + -t [id] "tail -n 10 -f" the output of the job. Last run if not specified. + -c [id] like -t, but shows all the lines. Last run if not specified. + -p [id] show the PID of the job. Last run if not specified. + -o [id] show the output file. Of last job run, if not specified. + -i [id] show job information. Of last job run, if not specified. + -s [id] show the job state. Of the last added, if not specified. + -r [id] remove a job. The last added, if not specified. + -w [id] wait for a job. The last added, if not specified. + -k [id] send SIGTERM to the job process group. The last run, if not specified. + -T send SIGTERM to all running job groups. (only available for root) + -u [id] put that job first. The last added, if not specified. + -U swap two jobs in the queue. + -h | --help show this help + -V show the program version Options adding jobs: + -B in case of full clients on the server, quit instead of waiting. -n don't store the output of the command. -E Keep stderr apart, in a name like the output file, but adding '.e'. -O Set name of the log file (without any path). @@ -177,8 +189,8 @@ Options adding jobs: -d the job will be run after the last job ends. -D the job will be run after the job of given IDs ends. -W the job will be run after the job of given IDs ends well (exit code 0). - -L name this task with a label, to be distinguished on listing. - -N number of slots required by the job (1 default). + -L [label] name this task with a label, to be distinguished on listing. + -N [num] number of slots required by the job (1 default). ``` ## Thanks From e9d45f21978ddd3c3a84544aef1dc5cc7ae57693 Mon Sep 17 00:00:00 2001 From: kylin Date: Fri, 12 Aug 2022 10:19:48 +0800 Subject: [PATCH 17/91] version 0.1.9 change the job_list --- execute.c | 5 +- jobs.c | 226 ++++++++++++++++++++++--------------------------- mail.c | 199 ++++++++++++++++++++++--------------------- main.c | 20 ++--- server.c | 2 + server_start.c | 14 +-- 6 files changed, 223 insertions(+), 243 deletions(-) diff --git a/execute.c b/execute.c index e8544b2..14e3c90 100644 --- a/execute.c +++ b/execute.c @@ -146,9 +146,10 @@ static void run_child(int fd_send_filename, const char *tmpdir) { label = command_line.label; } - outfname = malloc(1 + strlen(label) + strlen(".XXXXXX") + 1); + int len_outfname = 1 + strlen(label) + strlen(".XXXXXX") + 1; + outfname = malloc(len_outfname); - sprintf(outfname, "/%s.XXXXXX", label); + snprintf(outfname, len_outfname, "/%s.XXXXXX", label); if (command_line.store_output) { /* Prepare path */ diff --git a/jobs.c b/jobs.c index b83f78d..a232d89 100644 --- a/jobs.c +++ b/jobs.c @@ -29,7 +29,7 @@ struct Notify { }; /* Globals */ -static struct Job *firstjob = 0; +static struct Job firstjob = {0}; static struct Job *first_finished_job = 0; static int jobids = 1000; /* This is used for dependencies from jobs @@ -94,35 +94,35 @@ static struct Job *find_previous_job(const struct Job *final) { struct Job *p; /* Show Queued or Running jobs */ - p = firstjob; - while (p != 0) { + p = &firstjob; + while (p != NULL) { if (p->next == final) return p; p = p->next; } - return 0; + return NULL; } static struct Job *findjob(int jobid) { struct Job *p; /* Show Queued or Running jobs */ - p = firstjob; + p = firstjob.next; while (p != 0) { if (p->jobid == jobid) return p; p = p->next; } - return 0; + return NULL; } static struct Job *findjob_holding_client() { struct Job *p; /* Show Queued or Running jobs */ - p = firstjob; + p = firstjob.next; while (p != 0) { if (p->state == HOLDING_CLIENT) return p; @@ -151,7 +151,7 @@ static int count_not_finished_jobs() { struct Job *p; /* Show Queued or Running jobs */ - p = firstjob; + p = firstjob.next; while (p != 0) { ++count; p = p->next; @@ -180,7 +180,7 @@ void s_kill_all_jobs(int s) { s_count_running_jobs(s); /* send running job PIDs */ - p = firstjob; + p = firstjob.next; while (p != 0) { if (p->state == RUNNING) send(s, &p->pid, sizeof(int), 0); @@ -195,7 +195,7 @@ void s_count_running_jobs(int s) { struct Msg m = default_msg(); /* Count running jobs */ - p = firstjob; + p = firstjob.next; while (p != 0) { if (p->state == RUNNING) ++count; @@ -224,7 +224,7 @@ void s_get_label(int s, int jobid) { if (jobid == -1) { /* Find the last job added */ - p = firstjob; + p = firstjob.next; if (p != 0) while (p->next != 0) @@ -243,9 +243,8 @@ void s_get_label(int s, int jobid) { } if (p == 0) { - char tmp[50]; - sprintf(tmp, "Job %i not finished or not running.\n", jobid); - send_list_line(s, tmp); + snprintf(buff, 256, "Job %i not finished or not running.\n", jobid); + send_list_line(s, buff); return; } @@ -265,7 +264,7 @@ void s_send_cmd(int s, int jobid) { if (jobid == -1) { /* Find the last job added */ - p = firstjob; + p = firstjob.next; if (p != 0) while (p->next != 0) @@ -284,9 +283,8 @@ void s_send_cmd(int s, int jobid) { } if (p == 0) { - char tmp[50]; - sprintf(tmp, "Job %i not finished or not running.\n", jobid); - send_list_line(s, tmp); + snprintf(buff, 256, "Job %i not finished or not running.\n", jobid); + send_list_line(s, buff); return; } cmd = (char *)malloc(strlen(p->command) + 1); @@ -344,7 +342,7 @@ void s_list(int s, int user_id) { free(buffer); /* Show Queued or Running jobs */ - p = firstjob; + p = firstjob.next; while (p != 0) { if (p->state != HOLDING_CLIENT) { buffer = joblist_line(p); @@ -376,7 +374,7 @@ void s_list_all(int s) { free(buffer); /* Show Queued or Running jobs */ - p = firstjob; + p = firstjob.next; while (p != 0) { if (p->state != HOLDING_CLIENT) { buffer = joblist_line(p); @@ -402,7 +400,7 @@ void s_list_plain(int s) { char *buffer; /* Show Queued or Running jobs */ - p = firstjob; + p = firstjob.next; while (p != 0) { if (p->state != HOLDING_CLIENT) { buffer = joblist_line_plain(p); @@ -426,15 +424,7 @@ void s_list_plain(int s) { static struct Job *newjobptr() { struct Job *p; - if (firstjob == 0) { - firstjob = (struct Job *)malloc(sizeof(*firstjob)); - firstjob->next = 0; - firstjob->output_filename = 0; - firstjob->command = 0; - return firstjob; - } - - p = firstjob; + p = &firstjob; while (p->next != 0) p = p->next; @@ -451,7 +441,7 @@ static int find_last_jobid_in_queue(int neglect_jobid) { struct Job *p; int last_jobid = -1; - p = firstjob; + p = firstjob.next; while (p != 0) { if (p->jobid != neglect_jobid && p->jobid > last_jobid) last_jobid = p->jobid; @@ -641,18 +631,18 @@ int s_newjob(int s, struct Msg *m) { void s_removejob(int jobid) { struct Job *p; struct Job *newnext; - - if (firstjob->jobid == jobid) { + /* + if (firstjob.next.jobid == jobid) { struct Job *newfirst; - /* First job is to be removed */ - newfirst = firstjob->next; - destroy_job(firstjob); - firstjob = newfirst; - return; + // First job is to be removed // + newfirst = firstjob->next; + destroy_job(firstjob); + firstjob = newfirst; + return; } - - p = firstjob; + */ + p = &firstjob; /* Not first job */ while (p->next != 0) { if (p->next->jobid == jobid) @@ -683,7 +673,7 @@ int next_run_job() { return -1; /* If there are no jobs to run... */ - if (firstjob == 0) + if (firstjob.next == 0) return -1; /* Look for a runnable task */ @@ -692,7 +682,7 @@ int next_run_job() { if (user_queue[uid] == 0) { continue; } - p = firstjob; + p = firstjob.next; while (p != 0) { if (p->state == QUEUED) { if (p->depend_on_size) { @@ -843,20 +833,11 @@ void job_finished(const struct Result *result, int jobid) { /* Find the pointing node, to * update it removing the finished job. */ { - struct Job **jpointer = 0; + struct Job *jpointer = &firstjob; struct Job *newfirst = p->next; - if (firstjob == p) - jpointer = &firstjob; - else { - struct Job *p2; - p2 = firstjob; - while (p2 != 0) { - if (p2->next == p) { - jpointer = &(p2->next); - break; - } - p2 = p2->next; - } + + while (jpointer->next != p) { + jpointer = jpointer->next; } /* Add it to the finished queue (maybe temporarily) */ @@ -869,7 +850,7 @@ void job_finished(const struct Result *result, int jobid) { "queue list (jobid=%i)", p->jobid); - *jpointer = newfirst; + jpointer->next = newfirst; } } @@ -942,11 +923,11 @@ void s_job_info(int s, int jobid) { /* This means that we want the job info of the running task, or that * of the last job run */ if (busy_slots > 0) { - p = firstjob; + p = firstjob.next; if (p == 0) error("Internal state WAITING, but job not run." "firstjob = %x", - firstjob); + firstjob.next); } else { p = first_finished_job; if (p == 0) { @@ -957,7 +938,7 @@ void s_job_info(int s, int jobid) { p = p->next; } } else { - p = firstjob; + p = firstjob.next; while (p != 0 && p->jobid != jobid) p = p->next; @@ -970,9 +951,8 @@ void s_job_info(int s, int jobid) { } if (p == 0) { - char tmp[50]; - sprintf(tmp, "Job %i not finished or not running.\n", jobid); - send_list_line(s, tmp); + snprintf(buff, 256, "Job %i not finished or not running.\n", jobid); + send_list_line(s, buff); return; } @@ -1040,7 +1020,7 @@ void s_cont_user(int s, int uid) { user_max_slots[user_id] = abs(user_max_slots[user_id]); - struct Job *p = firstjob; + struct Job *p = firstjob.next; while (p != NULL) { if (p->user_id == user_id && p->state == RUNNING) { // p->state = HOLDING_CLIENT; @@ -1063,7 +1043,7 @@ void s_stop_user(int s, int uid) { user_max_slots[user_id] = -abs(user_max_slots[user_id]); - struct Job *p = firstjob; + struct Job *p = firstjob.next; while (p != NULL) { if (p->user_id == user_id && p->state == RUNNING) { // p->state = HOLDING_CLIENT; @@ -1093,11 +1073,11 @@ void s_send_output(int s, int jobid) { /* This means that we want the output info of the running task, or that * of the last job run */ if (busy_slots > 0) { - p = firstjob; + p = firstjob.next; if (p == 0) error("Internal state WAITING, but job not run." "firstjob = %x", - firstjob); + firstjob.next); } else { p = first_finished_job; if (p == 0) { @@ -1115,23 +1095,21 @@ void s_send_output(int s, int jobid) { } if (p == 0) { - char tmp[50]; if (jobid == -1) - sprintf(tmp, "The last job has not finished or is not running.\n"); + snprintf(buff, 256, "The last job has not finished or is not running.\n"); else - sprintf(tmp, "Job %i not finished or not running.\n", jobid); - send_list_line(s, tmp); + snprintf(buff, 256, "Job %i not finished or not running.\n", jobid); + send_list_line(s, buff); return; } if (p->state == SKIPPED) { - char tmp[50]; if (jobid == -1) - sprintf(tmp, "The last job was skipped due to a dependency.\n"); + snprintf(buff, 256, "The last job was skipped due to a dependency.\n"); else - sprintf(tmp, "Job %i was skipped due to a dependency.\n", jobid); - send_list_line(s, tmp); + snprintf(buff, 256, "Job %i was skipped due to a dependency.\n", jobid); + send_list_line(s, buff); return; } @@ -1166,11 +1144,11 @@ void notify_errorlevel(struct Job *p) { int s_remove_job(int s, int *jobid, int client_uid) { struct Job *p = 0; struct Msg m = default_msg(); - struct Job *before_p = 0; + struct Job *before_p = &firstjob; if (*jobid == -1) { /* Find the last job added */ - p = firstjob; + p = firstjob.next; if (p != 0) { while (p->next != 0) { before_p = p; @@ -1187,14 +1165,8 @@ int s_remove_job(int s, int *jobid, int client_uid) { } } } else { - p = firstjob; - if (p != 0) { - while (p->next != 0 && p->jobid != *jobid) { - before_p = p; - p = p->next; - } - } - + p = findjob(*jobid); + before_p = find_previous_job(p); /* If not found, look in the 'finished' list */ if (p == 0 || p->jobid != *jobid) { p = first_finished_job; @@ -1214,17 +1186,12 @@ int s_remove_job(int s, int *jobid, int client_uid) { client_uid = user_UID[p->user_id]; } - if (p == NULL || p == firstjob || (user_UID[p->user_id] != client_uid)) { + if (p == NULL || (user_UID[p->user_id] != client_uid)) { if (*jobid == -1) snprintf(buff, 256, "The last job cannot be removed.\n"); else snprintf(buff, 256, "The job %i cannot be removed.\n", *jobid); - if (p == firstjob && p->state == RUNNING) { - if (p->pid != 0) - kill(p->pid, SIGTERM); - snprintf(buff, 256, "The first job %i is removed.\n", *jobid); - } if (p != NULL) { int id = p->user_id; if (user_UID[id] != client_uid) { @@ -1237,9 +1204,18 @@ int s_remove_job(int s, int *jobid, int client_uid) { } if (p->state == RUNNING) { - if (p->pid != 0) + if (p->pid != 0 && (user_UID[p->user_id] == client_uid)) { kill(p->pid, SIGTERM); + if (*jobid == -1) + snprintf(buff, 256, "The last job is removed.\n"); + else + snprintf(buff, 256, "The job %i is removed.\n", *jobid); + send_list_line(s, buff); + return 0; + } + return 0; } + /* if (p == firstjob) { p->state = FINISHED; @@ -1522,7 +1498,7 @@ void s_wait_job(int s, int jobid) { if (jobid == -1) { /* Find the last job added */ - p = firstjob; + p = firstjob.next; if (p != 0) while (p->next != 0) @@ -1536,7 +1512,7 @@ void s_wait_job(int s, int jobid) { p = p->next; } } else { - p = firstjob; + p = firstjob.next; while (p != 0 && p->jobid != jobid) p = p->next; @@ -1549,12 +1525,11 @@ void s_wait_job(int s, int jobid) { } if (p == 0) { - char tmp[50]; if (jobid == -1) - sprintf(tmp, "The last job cannot be waited.\n"); + snprintf(buff, 256, "The last job cannot be waited.\n"); else - sprintf(tmp, "The job %i cannot be waited.\n", jobid); - send_list_line(s, tmp); + snprintf(buff, 256, "The job %i cannot be waited.\n", jobid); + send_list_line(s, buff); return; } @@ -1573,11 +1548,11 @@ void s_wait_running_job(int s, int jobid) { /* This means that we want the output info of the running task, or that * of the last job run */ if (busy_slots > 0) { - p = firstjob; + p = firstjob.next; if (p == 0) error("Internal state WAITING, but job not run." "firstjob = %x", - firstjob); + firstjob.next); } else { p = first_finished_job; if (p == 0) { @@ -1588,7 +1563,7 @@ void s_wait_running_job(int s, int jobid) { p = p->next; } } else { - p = firstjob; + p = firstjob.next; while (p != 0 && p->jobid != jobid) p = p->next; @@ -1601,12 +1576,11 @@ void s_wait_running_job(int s, int jobid) { } if (p == 0) { - char tmp[50]; if (jobid == -1) - sprintf(tmp, "The last job cannot be waited.\n"); + snprintf(buff, 256, "The last job cannot be waited.\n"); else - sprintf(tmp, "The job %i cannot be waited.\n", jobid); - send_list_line(s, tmp); + snprintf(buff, 256, "The job %i cannot be waited.\n", jobid); + send_list_line(s, buff); return; } @@ -1639,32 +1613,37 @@ void s_move_urgent(int s, int jobid) { if (jobid == -1) { /* Find the last job added */ - p = firstjob; + p = firstjob.next; if (p != 0) while (p->next != 0) p = p->next; } else { - p = firstjob; + p = firstjob.next; while (p != 0 && p->jobid != jobid) p = p->next; } - if (p == 0 || firstjob->next == 0) { - char tmp[50]; + // firstjob.next means no run job + if (p == 0 || firstjob.next == 0) { if (jobid == -1) - sprintf(tmp, "The last job cannot be urged.\n"); + snprintf(buff, 256, "The last job cannot be urged.\n"); else - sprintf(tmp, "The job %i cannot be urged.\n", jobid); - send_list_line(s, tmp); + snprintf(buff, 256, "The job %i cannot be urged.\n", jobid); + send_list_line(s, buff); return; } /* Interchange the pointers */ tmp1 = find_previous_job(p); + if (tmp1 == NULL) { + snprintf(buff, 256, "The job %i cannot be urged.\n", jobid); + send_list_line(s, buff); + return; + } tmp1->next = p->next; - p->next = firstjob->next; - firstjob->next = p; + p->next = firstjob.next; + firstjob.next = p; send_urgent_ok(s); } @@ -1676,10 +1655,10 @@ void s_swap_jobs(int s, int jobid1, int jobid2) { p1 = findjob(jobid1); p2 = findjob(jobid2); - if (p1 == 0 || p2 == 0 || p1 == firstjob || p2 == firstjob) { - char prev[60]; - sprintf(prev, "The jobs %i and %i cannot be swapped.\n", jobid1, jobid2); - send_list_line(s, prev); + if (p1 == NULL || p2 == NULL) { + snprintf(buff, 256, "The jobs %i and %i cannot be swapped.\n", jobid1, + jobid2); + send_list_line(s, buff); return; } @@ -1709,7 +1688,7 @@ void s_send_state(int s, int jobid) { if (jobid == -1) { /* Find the last job added */ - p = firstjob; + p = firstjob.next; if (p != 0) while (p->next != 0) @@ -1728,12 +1707,11 @@ void s_send_state(int s, int jobid) { } if (p == 0) { - char tmp[50]; if (jobid == -1) - sprintf(tmp, "The last job cannot be stated.\n"); + snprintf(buff, 256, "The last job cannot be stated.\n"); else - sprintf(tmp, "The job %i cannot be stated.\n", jobid); - send_list_line(s, tmp); + snprintf(buff, 256, "The job %i cannot be stated.\n", jobid); + send_list_line(s, buff); return; } @@ -1759,7 +1737,7 @@ void dump_jobs_struct(FILE *out) { fprintf(out, "New_jobs\n"); - p = firstjob; + p = firstjob.next; while (p != 0) { dump_job_struct(out, p); p = p->next; @@ -1816,7 +1794,7 @@ void joblist_dump(int fd) { write(fd, "\n", 1); /* Show Queued or Running jobs */ - p = firstjob; + p = firstjob.next; while (p != 0) { buffer = joblistdump_torun(p); write(fd, buffer, strlen(buffer)); diff --git a/mail.c b/mail.c index fa7c9e0..7945b7b 100644 --- a/mail.c +++ b/mail.c @@ -4,129 +4,128 @@ Please find the license in the provided COPYING file. */ +#include #include +#include +#include #include +#include +#include /* Needed for any main.h inclusion */ #include #include -#include #include -#include -#include -#include -#include /* Needed for any main.h inclusion */ #include "main.h" /* Returns the write pipe */ static int run_sendmail(const char *dest) { - int pid; - int p[2]; - - pipe(p); - - pid = fork(); - - switch (pid) { - case 0: /* Child */ - restore_sigmask(); - close(0); - close(1); - close(2); - close(p[1]); - dup2(p[0], 0); - execl("/usr/sbin/sendmail", "sendmail", "-oi", dest, NULL); - error("run sendmail"); - case -1: - error("fork sendmail"); - default: /* Parent */ - close(p[0]); - } - return p[1]; + int pid; + int p[2]; + + pipe(p); + + pid = fork(); + + switch (pid) { + case 0: /* Child */ + restore_sigmask(); + close(0); + close(1); + close(2); + close(p[1]); + dup2(p[0], 0); + execl("/usr/sbin/sendmail", "sendmail", "-oi", dest, NULL); + error("run sendmail"); + case -1: + error("fork sendmail"); + default: /* Parent */ + close(p[0]); + } + return p[1]; } static void write_header(int fd, const char *dest, const char *command, int jobid, int errorlevel) { - fd_nprintf(fd, 100, "From: Task Spooler \n"); - fd_nprintf(fd, 500, "To: %s\n", dest); - fd_nprintf(fd, 500, "Subject: the task %i finished with error %i. \n", jobid, - errorlevel); - fd_nprintf(fd, 500, "\nCommand: %s\n", command); - fd_nprintf(fd, 500, "Output:\n"); + fd_nprintf(fd, 100, "From: Task Spooler \n"); + fd_nprintf(fd, 500, "To: %s\n", dest); + fd_nprintf(fd, 500, "Subject: the task %i finished with error %i. \n", jobid, + errorlevel); + fd_nprintf(fd, 500, "\nCommand: %s\n", command); + fd_nprintf(fd, 500, "Output:\n"); } static void copy_output(int write_fd, const char *ofname) { - int file_fd; - char buffer[1000]; - int read_bytes; - int res; - - file_fd = open(ofname, O_RDONLY); - if (file_fd == -1) - error("mail: Cannot open the output file %s", ofname); - - do { - read_bytes = read(file_fd, buffer, 1000); - if (read_bytes > 0) { - res = write(write_fd, buffer, read_bytes); - if (res == -1) - warning("Cannot write to the mail pipe %i", write_fd); - } - } while (read_bytes > 0); - if (read_bytes == -1) - warning("Cannot read the output file %s from %i", ofname, file_fd); + int file_fd; + char buffer[1000]; + int read_bytes; + int res; + + file_fd = open(ofname, O_RDONLY); + if (file_fd == -1) + error("mail: Cannot open the output file %s", ofname); + + do { + read_bytes = read(file_fd, buffer, 1000); + if (read_bytes > 0) { + res = write(write_fd, buffer, read_bytes); + if (res == -1) + warning("Cannot write to the mail pipe %i", write_fd); + } + } while (read_bytes > 0); + if (read_bytes == -1) + warning("Cannot read the output file %s from %i", ofname, file_fd); } void hook_on_finish(int jobid, int errorlevel, const char *ofname, const char *command) { - char *onfinish; - int pid; - char sjobid[20]; - char serrorlevel[20]; - int status; - - onfinish = getenv("TS_ONFINISH"); - if (onfinish == NULL) - return; - - pid = fork(); - - switch (pid) { - case 0: /* Child */ - restore_sigmask(); - sprintf(sjobid, "%i", jobid); - sprintf(serrorlevel, "%i", errorlevel); - execlp(onfinish, onfinish, sjobid, serrorlevel, ofname, command, - NULL); - case -1: - error("fork on finish"); - default: /* Parent */ - wait(&status); - } + char *onfinish; + int pid; + char sjobid[20]; + char serrorlevel[20]; + int status; + + onfinish = getenv("TS_ONFINISH"); + if (onfinish == NULL) + return; + + pid = fork(); + + switch (pid) { + case 0: /* Child */ + restore_sigmask(); + snprintf(sjobid, 19, "%i", jobid); + snprintf(serrorlevel, 19, "%i", errorlevel); + execlp(onfinish, onfinish, sjobid, serrorlevel, ofname, command, NULL); + case -1: + error("fork on finish"); + default: /* Parent */ + wait(&status); + } } void send_mail(int jobid, int errorlevel, const char *ofname, const char *command) { - char to[101]; - char *user; - char *env_to; - int write_fd; - int status; - - env_to = getenv("TS_MAILTO"); - - if (env_to == NULL || strlen(env_to) > 100) { - user = getenv("USER"); - if (user == NULL) - user = "nobody"; - - strcpy(to, user); - /*strcat(to, "@localhost");*/ - } else - strcpy(to, env_to); - - write_fd = run_sendmail(to); - write_header(write_fd, to, command, jobid, errorlevel); - copy_output(write_fd, ofname); - close(write_fd); - wait(&status); + char to[101]; + char *user; + char *env_to; + int write_fd; + int status; + + env_to = getenv("TS_MAILTO"); + + if (env_to == NULL || strlen(env_to) > 100) { + user = getenv("USER"); + if (user == NULL) + user = "nobody"; + + strcpy(to, user); + /*strcat(to, "@localhost");*/ + } else + strcpy(to, env_to); + + write_fd = run_sendmail(to); + write_header(write_fd, to, command, jobid, errorlevel); + copy_output(write_fd, ofname); + close(write_fd); + wait(&status); } diff --git a/main.c b/main.c index 6d8c3bc..9b0225b 100644 --- a/main.c +++ b/main.c @@ -31,7 +31,7 @@ struct CommandLine command_line; int term_width; /* Globals for the environment of getopt */ -static char getopt_env[] = "POSIXLY_CORRECT=YES"; +static char getopt_env[20] = "POSIXLY_CORRECT=YES"; static char *old_getopt_env; static char version[1024]; @@ -39,15 +39,15 @@ static char version[1024]; static void init_version() { #ifdef TS_VERSION char *ts_version = TS_MAKE_STR(TS_VERSION); - sprintf(version, - "Task Spooler %s - a task queue system for the unix user.\n" - "Copyright (C) 2007-2020 Duc Nguyen - Lluis Batlle i Rossell", - ts_version); + snprintf(version, 1024, + "Task Spooler %s - a task queue system for the unix user.\n" + "Copyright (C) 2007-2020 Duc Nguyen - Lluis Batlle i Rossell", + ts_version); #else - sprintf(version, - "Task Spooler %s - a task queue system for the unix user.\n" - "Copyright (C) 2007-2020 Duc Nguyen - Lluis Batlle i Rossell", - TS_VERSION_FALLBACK); + snprintf(version, 1024, + "Task Spooler %s - a task queue system for the unix user.\n" + "Copyright (C) 2007-2020 Duc Nguyen - Lluis Batlle i Rossell", + TS_VERSION_FALLBACK); #endif } @@ -596,7 +596,7 @@ static void unset_getopt_env() { /* Wipe the string from the environment */ putenv("POSIXLY_CORRECT"); } else - sprintf(getopt_env, "POSIXLY_CORRECT=%s", old_getopt_env); + snprintf(getopt_env, 20, "POSIXLY_CORRECT=%s", old_getopt_env); } static void get_terminal_width() { diff --git a/server.c b/server.c index 581a377..4da45ae 100644 --- a/server.c +++ b/server.c @@ -571,6 +571,8 @@ static enum Break client_read(int index) { s_swap_jobs(s, m.u.swap.jobid1, m.u.swap.jobid2); } } + close(s); + remove_connection(index); break; case GET_STATE: s_send_state(s, m.u.jobid); diff --git a/server_start.c b/server_start.c index 565dc2d..0ae8026 100644 --- a/server_start.c +++ b/server_start.c @@ -51,15 +51,13 @@ void create_socket_path(char **path) { if (tmpdir == NULL) tmpdir = "/tmp"; - // sprintf(userid, "%u", (unsigned int)getuid()); - /* Calculate the size */ size = strlen(tmpdir) + strlen("/socket-ts.") + strlen(userid) + 1; /* Freed after preparing the socket address */ *path = (char *)malloc(size); - sprintf(*path, "%s/socket-ts.%s", tmpdir, userid); + snprintf(*path, size - 1, "%s/socket-ts.%s", tmpdir, userid); should_check_owner = 1; } @@ -123,10 +121,12 @@ static int fork_server() { case -1: /* Error */ return -1; default: /* Parent */ - printf("Start tast-spooler server from root[%d]\n", client_uid); - printf(" Read user file from %s [TS_USER_PATH]\n", get_user_path()); - printf(" Write log file to %s [TS_LOGFILE_PATH]\n\n", - set_server_logfile()); + if (client_uid == 0) { + printf("Start tast-spooler server from root[%d]\n", client_uid); + printf(" Read user file from %s [TS_USER_PATH]\n", get_user_path()); + printf(" Write log file to %s [TS_LOGFILE_PATH]\n\n", + set_server_logfile()); + } close(p[1]); } /* Return the read fd */ From 3bf29c1018fa17ca4ca95780861149e05a298d08 Mon Sep 17 00:00:00 2001 From: kylin Date: Fri, 12 Aug 2022 15:07:51 +0800 Subject: [PATCH 18/91] version 0.2.0 modify the first_finished_job --- README.md | 6 +- client.c | 15 ++++- jobs.c | 154 ++++++++++++++++++++++++++----------------------- list.c | 7 +-- main.c | 6 +- main.h | 6 +- server.c | 14 ++++- server_start.c | 1 - user.c | 8 +-- user.txt | 6 +- 10 files changed, 132 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index 32642db..04c6d83 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,7 @@ Duc Nguyen took the project and develops a GPU-support version. See below or `man ts` for more details. ``` +usage: ts [action] [-ngfmdE] [-L ] [-D ] [cmd...] Env vars: TS_SOCKET the path to the unix socket used by the ts command. TS_MAILTO where to mail the result (on -m). Local user by default. @@ -136,7 +137,8 @@ Env vars: TS_SLOTS amount of jobs which can run at once, read on server start. TS_USER_PATH path to the user configuration file, read on server starts. TS_LOGFILE_PATH path to the job log file, read on server starts - TS_JOBID The first job ID (default: 1000), read on server starts. + TS_FIRST_JOBID The first job ID (default: 1000), read on server starts. + TS_SORTJOBS Switch to control the job sequence sort, read on server starts. TMPDIR directory where to place the output files and the default socket. Long option actions: --getenv [var] get the value of the specified variable in server environment. @@ -151,7 +153,7 @@ Long option actions: --plain list jobs in plain tab-separated texts. --hold_job [jobid] hold on a task. --restart_job [jobid] restart a task. - --lock Locker the server (Timeout: 30 sec.). For Root, timeout is infinity. + --lock Locker the server (Timeout: 30 sec.)git For Root, timeout is infinity. --unlock Unlocker the server. --stop [user] For normal user, pause all tasks and lock the account. For root, to lock all users or single [user]. diff --git a/client.c b/client.c index 29bb1fc..87af434 100644 --- a/client.c +++ b/client.c @@ -386,7 +386,7 @@ void c_shutdown_server() { return; } char buf[10]; - printf("Do you want to kill the taskspooler server? (Yes/n) "); + printf("Do you want to kill the task-spooler server? (Yes/no) "); scanf("%3s", buf); if (strcmp(buf, "Yes") != 0) { return; @@ -400,8 +400,19 @@ void c_shutdown_server() { void c_clear_finished() { struct Msg m = default_msg(); - + if (m.uid == 0) { + char buf[10]; + printf("Do you want to clear all the finished jobs on the task-spooler " + "server? (Yes/no) "); + scanf("%3s", buf); + if (strcmp(buf, "Yes") != 0) { + return; + } else { + printf("Clear the finished!\n"); + } + } m.type = CLEAR_FINISHED; + send_msg(server_socket, &m); } diff --git a/jobs.c b/jobs.c index a232d89..77164e9 100644 --- a/jobs.c +++ b/jobs.c @@ -30,7 +30,7 @@ struct Notify { /* Globals */ static struct Job firstjob = {0}; -static struct Job *first_finished_job = 0; +static struct Job first_finished_job = {0}; static int jobids = 1000; /* This is used for dependencies from jobs * already out of the queue */ @@ -48,7 +48,8 @@ static struct Job *get_job(int jobid); void notify_errorlevel(struct Job *p); -void set_jobids(int i) { jobids = i; } +void s_set_jobids(int i) { jobids = i; } + static void destroy_job(struct Job *p) { free(p->notify_errorlevel_to); free(p->command); @@ -90,6 +91,28 @@ static void send_swap_jobs_ok(int s) { send_msg(s, &m); } +void s_sort_jobs() { + struct Job queue; + struct Job *p_queue, *p_run; + struct Job *p; + + p_run = &firstjob; + p_queue = &queue; + + p = firstjob.next; + while (p != NULL) { + if (p->state == RUNNING) { + p_run->next = p; + p_run = p; + } else { + p_queue->next = p; + p_queue = p; + } + p = p->next; + } + p_run->next = queue.next; +} + static struct Job *find_previous_job(const struct Job *final) { struct Job *p; @@ -136,7 +159,7 @@ static struct Job *find_finished_job(int jobid) { struct Job *p; /* Show Queued or Running jobs */ - p = first_finished_job; + p = first_finished_job.next; while (p != 0) { if (p->jobid == jobid) return p; @@ -232,7 +255,7 @@ void s_get_label(int s, int jobid) { /* Look in finished jobs if needed */ if (p == 0) { - p = first_finished_job; + p = first_finished_job.next; if (p != 0) while (p->next != 0) p = p->next; @@ -272,7 +295,7 @@ void s_send_cmd(int s, int jobid) { /* Look in finished jobs if needed */ if (p == 0) { - p = first_finished_job; + p = first_finished_job.next; if (p != 0) while (p->next != 0) p = p->next; @@ -345,21 +368,25 @@ void s_list(int s, int user_id) { p = firstjob.next; while (p != 0) { if (p->state != HOLDING_CLIENT) { - buffer = joblist_line(p); - if (p->user_id == user_id) + if (p->user_id == user_id) { + buffer = joblist_line(p); send_list_line(s, buffer); - free(buffer); + free(buffer); + } } p = p->next; } - p = first_finished_job; - + p = first_finished_job.next; + if (p != NULL && firstjob.next != NULL) + send_list_line(s, "----- Finished -----\n"); /* Show Finished jobs */ while (p != 0) { - buffer = joblist_line(p); - send_list_line(s, buffer); - free(buffer); + if (p->user_id == user_id) { + buffer = joblist_line(p); + send_list_line(s, buffer); + free(buffer); + } p = p->next; } } @@ -384,7 +411,9 @@ void s_list_all(int s) { p = p->next; } - p = first_finished_job; + p = first_finished_job.next; + if (p != NULL && firstjob.next != NULL) + send_list_line(s, "\n ----- Finished -----\n"); /* Show Finished jobs */ while (p != 0) { @@ -410,7 +439,7 @@ void s_list_plain(int s) { p = p->next; } - p = first_finished_job; + p = first_finished_job.next; /* Show Finished jobs */ while (p != 0) { @@ -456,7 +485,7 @@ static int find_last_stored_jobid_finished() { struct Job *p; int last_jobid = -1; - p = first_finished_job; + p = first_finished_job.next; while (p != 0) { if (p->jobid > last_jobid) last_jobid = p->jobid; @@ -726,26 +755,20 @@ static int get_max_finished_jobs() { limit = getenv("TS_MAXFINISHED"); if (limit == NULL) return 1000; - return abs(atoi(limit)); + int num = abs(atoi(limit)); + if (num < 1) + num = 1000; + return num; } /* Add the job to the finished queue. */ static void new_finished_job(struct Job *j) { struct Job *p; - int count, max; + int count = 0, max; max = get_max_finished_jobs(); - count = 0; - if (first_finished_job == 0 && count < max) { - first_finished_job = j; - first_finished_job->next = 0; - return; - } - - ++count; - - p = first_finished_job; + p = &first_finished_job; while (p->next != 0) { p = p->next; ++count; @@ -754,8 +777,8 @@ static void new_finished_job(struct Job *j) { /* If too many jobs, wipe out the first */ if (count >= max) { struct Job *tmp; - tmp = first_finished_job; - first_finished_job = first_finished_job->next; + tmp = first_finished_job.next; + first_finished_job.next = tmp->next; destroy_job(tmp); } p->next = j; @@ -855,21 +878,16 @@ void job_finished(const struct Result *result, int jobid) { } void s_clear_finished(int user_id) { - struct Job newjob; - newjob.next = NULL; - struct Job *p, *other_user_job = &newjob; - if (first_finished_job == NULL) + struct Job *p, *other_user_job = &first_finished_job; + if (first_finished_job.next == NULL) return; - p = first_finished_job; - if (p->user_id == user_id) { - first_finished_job = NULL; - } - + p = first_finished_job.next; + other_user_job->next = NULL; while (p != NULL) { struct Job *tmp; tmp = p->next; - if (p->user_id == user_id) { + if (p->user_id == user_id || user_id == -100) { destroy_job(p); } else { other_user_job->next = p; @@ -877,7 +895,7 @@ void s_clear_finished(int user_id) { } p = tmp; } - first_finished_job = newjob.next; + other_user_job->next = NULL; } void s_process_runjob_ok(int jobid, char *oname, int pid) { @@ -929,7 +947,7 @@ void s_job_info(int s, int jobid) { "firstjob = %x", firstjob.next); } else { - p = first_finished_job; + p = first_finished_job.next; if (p == 0) { send_list_line(s, "No jobs.\n"); return; @@ -944,7 +962,7 @@ void s_job_info(int s, int jobid) { /* Look in finished jobs if needed */ if (p == 0) { - p = first_finished_job; + p = first_finished_job.next; while (p != 0 && p->jobid != jobid) p = p->next; } @@ -1079,7 +1097,7 @@ void s_send_output(int s, int jobid) { "firstjob = %x", firstjob.next); } else { - p = first_finished_job; + p = first_finished_job.next; if (p == 0) { send_list_line(s, "No jobs.\n"); return; @@ -1156,7 +1174,8 @@ int s_remove_job(int s, int *jobid, int client_uid) { } } else { /* last 'finished' */ - p = first_finished_job; + p = first_finished_job.next; + before_p = &first_finished_job; if (p) { while (p->next != 0) { before_p = p; @@ -1169,7 +1188,8 @@ int s_remove_job(int s, int *jobid, int client_uid) { before_p = find_previous_job(p); /* If not found, look in the 'finished' list */ if (p == 0 || p->jobid != *jobid) { - p = first_finished_job; + p = first_finished_job.next; + before_p = &first_finished_job; if (p != 0) { while (p->next != 0 && p->jobid != *jobid) { before_p = p; @@ -1237,10 +1257,7 @@ int s_remove_job(int s, int *jobid, int client_uid) { check_notify_list(m.u.jobid); /* Update the list pointers */ - if (p == first_finished_job) - first_finished_job = p->next; - else - before_p->next = p->next; + before_p->next = p->next; destroy_job(p); @@ -1445,22 +1462,17 @@ void s_remove_notification(int s) { } static void destroy_finished_job(struct Job *j) { - if (j == first_finished_job) - first_finished_job = j->next; - else { - struct Job *i; - for (i = first_finished_job; i != 0; ++i) { - if (i->next == j) { - i->next = j->next; - break; - } - } - if (i == 0) { - error("Cannot destroy the expected job %i", j->jobid); + struct Job *p = &first_finished_job; + while (p->next != 0) { + if (p->next != j) { + p = p->next; + } else { + p->next = j->next; + destroy_job(j); + return; } } - - destroy_job(j); + error("Cannot destroy the expected job %i", j->jobid); } /* This is called when a job finishes */ @@ -1506,7 +1518,7 @@ void s_wait_job(int s, int jobid) { /* Look in finished jobs if needed */ if (p == 0) { - p = first_finished_job; + p = first_finished_job.next; if (p != 0) while (p->next != 0) p = p->next; @@ -1518,7 +1530,7 @@ void s_wait_job(int s, int jobid) { /* Look in finished jobs if needed */ if (p == 0) { - p = first_finished_job; + p = first_finished_job.next; while (p != 0 && p->jobid != jobid) p = p->next; } @@ -1554,7 +1566,7 @@ void s_wait_running_job(int s, int jobid) { "firstjob = %x", firstjob.next); } else { - p = first_finished_job; + p = first_finished_job.next; if (p == 0) { send_list_line(s, "No jobs.\n"); return; @@ -1569,7 +1581,7 @@ void s_wait_running_job(int s, int jobid) { /* Look in finished jobs if needed */ if (p == 0) { - p = first_finished_job; + p = first_finished_job.next; while (p != 0 && p->jobid != jobid) p = p->next; } @@ -1696,7 +1708,7 @@ void s_send_state(int s, int jobid) { /* Look in finished jobs if needed */ if (p == 0) { - p = first_finished_job; + p = first_finished_job.next; if (p != 0) while (p->next != 0) p = p->next; @@ -1743,7 +1755,7 @@ void dump_jobs_struct(FILE *out) { p = p->next; } - p = first_finished_job; + p = first_finished_job.next; while (p != 0) { dump_job_struct(out, p); p = p->next; @@ -1782,7 +1794,7 @@ void joblist_dump(int fd) { write(fd, buffer, strlen(buffer)); /* Show Finished jobs */ - p = first_finished_job; + p = first_finished_job.next; while (p != 0) { buffer = joblist_line(p); write(fd, "# ", 2); diff --git a/list.c b/list.c index 318929c..2be20da 100644 --- a/list.c +++ b/list.c @@ -182,16 +182,15 @@ static char *print_noresult(const struct Job *p) { char *cmd = shorten(p->command, cmd_len); if (p->label) { char *label = shorten(p->label, 10); - snprintf(line, maxlen, - "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-20s %d | %s\n", p->jobid, - jobstate, p->num_slots, uname, label, real_ms, unit, cmd, p->pid, + snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-20s | %s\n", + p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, output_filename); free(label); free(cmd); } else { char *cmd = shorten(p->command, cmd_len); char *label = "(..)"; - snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-20s | %s\n", + snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-20s| %s\n", p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, output_filename); free(cmd); diff --git a/main.c b/main.c index 9b0225b..5887f15 100644 --- a/main.c +++ b/main.c @@ -493,8 +493,11 @@ static void print_help(const char *cmd) { "starts.\n"); printf(" TS_LOGFILE_PATH path to the job log file, read on server " "starts\n"); - printf(" TS_JOBID The first job ID (default: 1000), read on server " + printf(" TS_FIRST_JOBID The first job ID (default: 1000), read on server " "starts.\n"); + printf( + " TS_SORTJOBS Switch to control the job sequence sort, read on server " + "starts.\n"); printf(" TMPDIR directory where to place the output files and the " "default socket.\n"); printf("Long option actions:\n"); @@ -607,6 +610,7 @@ static void get_terminal_width() { int main(int argc, char **argv) { int errorlevel = 0; + jobsort_flag = 0; user_locker = -1; client_uid = getuid(); // printf("client_uid = %u\n", client_uid); diff --git a/main.h b/main.h index a13f225..624f336 100644 --- a/main.h +++ b/main.h @@ -497,7 +497,7 @@ void c_refresh_user(); const char *get_user_path(); const char *set_server_logfile(); void write_logfile(const struct Job *p); -int get_env_jobid(); +int get_env(const char *env, int v0); long str2int(const char *str); void debug_write(const char *str); const char *uid2user_name(int uid); @@ -505,6 +505,7 @@ const char *uid2user_name(int uid); /* locker */ int user_locker; time_t locker_time; +int jobsort_flag; /* jobs.c */ void s_user_status_all(int s); @@ -520,7 +521,8 @@ void s_restart_job(int s, int jobid, int uid); void s_lock_server(int s, int uid); void s_unlock_server(int s, int uid); int s_check_locker(int s, int uid); -void set_jobids(int i); +void s_set_jobids(int i); +void s_sort_jobs(); /* client.c */ void c_list_jobs_all(); diff --git a/server.c b/server.c index 4da45ae..807ef7c 100644 --- a/server.c +++ b/server.c @@ -205,7 +205,9 @@ void server_main(int notify_fd, char *_path) { user_queue[i] = 0; } - set_jobids(get_env_jobid()); + s_set_jobids(get_env("TS_FIRST_JOBID", 1000)); + jobsort_flag = get_env("TS_SORTJOBS", 0); + read_user_file(get_user_path()); set_server_logfile(); set_socket_model(_path); @@ -516,6 +518,10 @@ static enum Break client_read(int index) { case CLEAR_FINISHED: if (user_id != -1) s_clear_finished(user_id); + if (m.uid == 0) { + // clear all finished by all users + s_clear_finished(-100); + } break; case ASK_OUTPUT: s_send_output(s, m.u.jobid); @@ -553,6 +559,10 @@ static enum Break client_read(int index) { if (m.uid == 0 || m.uid == s_get_job_uid(m.u.jobid)) { s_move_urgent(s, m.u.jobid); } + if (jobsort_flag) + s_sort_jobs(); + close(s); + remove_connection(index); break; case SET_MAX_SLOTS: if (m.uid == 0) @@ -571,6 +581,8 @@ static enum Break client_read(int index) { s_swap_jobs(s, m.u.swap.jobid1, m.u.swap.jobid2); } } + if (jobsort_flag) + s_sort_jobs(); close(s); remove_connection(index); break; diff --git a/server_start.c b/server_start.c index 0ae8026..a471a2d 100644 --- a/server_start.c +++ b/server_start.c @@ -45,7 +45,6 @@ void create_socket_path(char **path) { } /* ... if the $TS_SOCKET doesn't exist ... */ - /* Create the path */ tmpdir = getenv("TMPDIR"); if (tmpdir == NULL) diff --git a/user.c b/user.c index 8df2819..a767adf 100644 --- a/user.c +++ b/user.c @@ -23,15 +23,15 @@ const char *get_user_path() { } } -int get_env_jobid() { +int get_env(const char *env, int v0) { char *str; - str = getenv("TS_JOBID"); + str = getenv(env); if (str == NULL || strlen(str) == 0) { - return 1000; + return v0; } else { int i = atoi(str); if (i < 0) - i = 1000; + i = v0; return i; } } diff --git a/user.txt b/user.txt index 5de7503..5a151ae 100644 --- a/user.txt +++ b/user.txt @@ -1,4 +1,4 @@ #uid user maxslot -1000 Kylin 3 -2 user 100 -34 user2 30 +1000 Kylin 10 +1001 test0 100 +34 user2 30 From 9f0a8d7e5ef245de93ffe6fdbcd1dd336f78a386 Mon Sep 17 00:00:00 2001 From: kylin Date: Sat, 13 Aug 2022 23:37:09 +0800 Subject: [PATCH 19/91] version 0.2.0 modify the first_finished_job --- user.c | 37 ++++++++++++++++++------------------- user.h | 2 +- user.txt | 4 ++++ 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/user.c b/user.c index a767adf..8a37ee5 100644 --- a/user.c +++ b/user.c @@ -84,6 +84,14 @@ void debug_write(const char *str) { fclose(f); } +static int find_user_by_name(const char *name) { + for (int i = 0; i < user_number; i++) { + if (strcmp(user_name[i], name) == 0) + return i; + } + return -1; +} + void read_user_file(const char *path) { server_uid = getuid(); if (server_uid != 0) { @@ -99,7 +107,6 @@ void read_user_file(const char *path) { int UID, slots; char name[USER_NAME_WIDTH]; - int i_number = 0; while ((read = getline(&line, &len, fp)) != -1) { if (line[0] == '#') continue; @@ -107,30 +114,22 @@ void read_user_file(const char *path) { int res = sscanf(line, "%d %256s %d", &UID, name, &slots); if (res != 3) { printf("error in read %s at line %s", path, line); - exit(0); + continue; } else { - if (user_max_slots[i_number] != 0 && user_UID[i_number] != UID) { - i_number++; - continue; + int user_id = find_user_by_name(name); + if (user_id == -1) { + if (user_number >= USER_MAX) + continue; + user_id = user_number; + user_number++; } - user_UID[i_number] = UID; - user_max_slots[i_number] = slots; - strncpy(user_name[i_number], name, USER_NAME_WIDTH); - - // printf("%d %s %d\n", user_ID[user_number], user_name[user_number], - // user_slot[user_number]); - // user_busy[user_number] = 0; - // user_jobs[user_number] = 0; - // user_queue[user_number] = 0; - i_number++; + user_UID[user_id] = UID; + user_max_slots[user_id] = slots; + strncpy(user_name[user_id], name, USER_NAME_WIDTH - 1); } } - if (i_number > user_number) { - user_number = i_number; - } - fclose(fp); if (line) free(line); diff --git a/user.h b/user.h index 135da9c..ecfce36 100644 --- a/user.h +++ b/user.h @@ -1,5 +1,5 @@ #define USER_NAME_WIDTH 256 -#define USER_MAX 50 +#define USER_MAX 100 char user_name[USER_MAX][USER_NAME_WIDTH]; int server_uid; diff --git a/user.txt b/user.txt index 5a151ae..9a6a6d1 100644 --- a/user.txt +++ b/user.txt @@ -1,4 +1,8 @@ #uid user maxslot 1000 Kylin 10 +# 1231 +qeqe eqe q +3021 test1 10 1001 test0 100 34 user2 30 + From 67bcba338c7f54e485f70058d5a0472af63697f2 Mon Sep 17 00:00:00 2001 From: kylin Date: Sat, 13 Aug 2022 23:47:11 +0800 Subject: [PATCH 20/91] version 0.2.1 modify the user refresh --- main.c | 5 +++-- user.c | 15 ++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/main.c b/main.c index 5887f15..f1c6612 100644 --- a/main.c +++ b/main.c @@ -536,8 +536,9 @@ static void print_help(const char *cmd) { printf("Actions:\n"); printf(" -A Show all users information\n"); - printf(" -X Refresh the user configuration (only available for " - "root)\n"); + printf( + " -X Refresh the user config by UID (Max. 100 users and only " + "available for root)\n"); printf(" -K kill the task spooler server (only available for " "root)\n"); printf(" -C clear the list of finished jobs for current user\n"); diff --git a/user.c b/user.c index 8a37ee5..a5eb6b1 100644 --- a/user.c +++ b/user.c @@ -84,6 +84,7 @@ void debug_write(const char *str) { fclose(f); } +/* static int find_user_by_name(const char *name) { for (int i = 0; i < user_number; i++) { if (strcmp(user_name[i], name) == 0) @@ -91,6 +92,7 @@ static int find_user_by_name(const char *name) { } return -1; } +*/ void read_user_file(const char *path) { server_uid = getuid(); @@ -116,17 +118,20 @@ void read_user_file(const char *path) { printf("error in read %s at line %s", path, line); continue; } else { - int user_id = find_user_by_name(name); + int user_id = get_user_id(UID); if (user_id == -1) { if (user_number >= USER_MAX) continue; + user_id = user_number; user_number++; - } - user_UID[user_id] = UID; - user_max_slots[user_id] = slots; - strncpy(user_name[user_id], name, USER_NAME_WIDTH - 1); + user_UID[user_id] = UID; + user_max_slots[user_id] = slots; + strncpy(user_name[user_id], name, USER_NAME_WIDTH - 1); + } else { + user_max_slots[user_id] = slots; + } } } From 7ecaaee75c512ab89bf3a8198382efbc0b1620f7 Mon Sep 17 00:00:00 2001 From: kylin Date: Sat, 13 Aug 2022 23:57:41 +0800 Subject: [PATCH 21/91] version 0.2.1 fixed the bug on the set max slots --- client.c | 3 +- jobs.c | 86 +++++++++++++++++++++++++++++--------------------------- main.c | 8 +++++- main.h | 2 +- server.c | 6 ++-- 5 files changed, 57 insertions(+), 48 deletions(-) diff --git a/client.c b/client.c index 87af434..646d859 100644 --- a/client.c +++ b/client.c @@ -653,7 +653,6 @@ int c_wait_running_job() { void c_send_max_slots(int max_slots) { struct Msg m = default_msg(); - /* Send the request */ m.type = SET_MAX_SLOTS; m.u.max_slots = command_line.max_slots; @@ -675,7 +674,7 @@ void c_get_max_slots() { error("Error in move_urgent"); switch (m.type) { case GET_MAX_SLOTS_OK: - printf("%i\n", m.u.max_slots); + printf("Max slots: %i\n", m.u.max_slots); return; default: warning("Wrong internal message in get_max_slots"); diff --git a/jobs.c b/jobs.c index 77164e9..91830d6 100644 --- a/jobs.c +++ b/jobs.c @@ -266,7 +266,7 @@ void s_get_label(int s, int jobid) { } if (p == 0) { - snprintf(buff, 256, "Job %i not finished or not running.\n", jobid); + snprintf(buff, 255, "Job %i not finished or not running.\n", jobid); send_list_line(s, buff); return; } @@ -306,7 +306,7 @@ void s_send_cmd(int s, int jobid) { } if (p == 0) { - snprintf(buff, 256, "Job %i not finished or not running.\n", jobid); + snprintf(buff, 255, "Job %i not finished or not running.\n", jobid); send_list_line(s, buff); return; } @@ -969,7 +969,7 @@ void s_job_info(int s, int jobid) { } if (p == 0) { - snprintf(buff, 256, "Job %i not finished or not running.\n", jobid); + snprintf(buff, 255, "Job %i not finished or not running.\n", jobid); send_list_line(s, buff); return; } @@ -1049,7 +1049,7 @@ void s_cont_user(int s, int uid) { p = p->next; } - snprintf(buff, 256, "Resume user: [%d] %s\n", uid, user_name[user_id]); + snprintf(buff, 255, "Resume user: [%d] %s\n", uid, user_name[user_id]); send_list_line(s, buff); } @@ -1071,7 +1071,7 @@ void s_stop_user(int s, int uid) { char *label = "(...)"; if (p->label != NULL) label = p->label; - snprintf(buff, 256, "Error in stop %s [%d] %s | %s\n", + snprintf(buff, 255, "Error in stop %s [%d] %s | %s\n", user_name[user_id], p->jobid, label, p->command); send_list_line(s, buff); } @@ -1079,7 +1079,7 @@ void s_stop_user(int s, int uid) { p = p->next; } - snprintf(buff, 256, "Lock user: [%d] %s\n", uid, user_name[user_id]); + snprintf(buff, 255, "Lock user: [%d] %s\n", uid, user_name[user_id]); send_list_line(s, buff); } @@ -1114,19 +1114,19 @@ void s_send_output(int s, int jobid) { if (p == 0) { if (jobid == -1) - snprintf(buff, 256, "The last job has not finished or is not running.\n"); + snprintf(buff, 255, "The last job has not finished or is not running.\n"); else - snprintf(buff, 256, "Job %i not finished or not running.\n", jobid); + snprintf(buff, 255, "Job %i not finished or not running.\n", jobid); send_list_line(s, buff); return; } if (p->state == SKIPPED) { if (jobid == -1) - snprintf(buff, 256, "The last job was skipped due to a dependency.\n"); + snprintf(buff, 255, "The last job was skipped due to a dependency.\n"); else - snprintf(buff, 256, "Job %i was skipped due to a dependency.\n", jobid); + snprintf(buff, 255, "Job %i was skipped due to a dependency.\n", jobid); send_list_line(s, buff); return; } @@ -1209,13 +1209,13 @@ int s_remove_job(int s, int *jobid, int client_uid) { if (p == NULL || (user_UID[p->user_id] != client_uid)) { if (*jobid == -1) - snprintf(buff, 256, "The last job cannot be removed.\n"); + snprintf(buff, 255, "The last job cannot be removed.\n"); else - snprintf(buff, 256, "The job %i cannot be removed.\n", *jobid); + snprintf(buff, 255, "The job %i cannot be removed.\n", *jobid); if (p != NULL) { int id = p->user_id; if (user_UID[id] != client_uid) { - snprintf(buff, 256, "The job %i belongs to user:%s not uid:%d.\n", + snprintf(buff, 255, "The job %i belongs to user:%s not uid:%d.\n", *jobid, user_name[id], client_uid); } } @@ -1227,9 +1227,9 @@ int s_remove_job(int s, int *jobid, int client_uid) { if (p->pid != 0 && (user_UID[p->user_id] == client_uid)) { kill(p->pid, SIGTERM); if (*jobid == -1) - snprintf(buff, 256, "The last job is removed.\n"); + snprintf(buff, 255, "The last job is removed.\n"); else - snprintf(buff, 256, "The job %i is removed.\n", *jobid); + snprintf(buff, 255, "The job %i is removed.\n", *jobid); send_list_line(s, buff); return 0; } @@ -1336,27 +1336,27 @@ void s_lock_server(int s, int uid) { if (uid == 0) { user_locker = 0; locker_time = time(NULL); - snprintf(buff, 256, "lock the task-spooler server by Root\n"); + snprintf(buff, 255, "lock the task-spooler server by Root\n"); } else { if (user_locker == -1) { int user_id = get_user_id(uid); if (user_id != -1) { user_locker = uid; locker_time = time(NULL); - snprintf(buff, 256, "lock the task-spooler server by `%s`\n", + snprintf(buff, 255, "lock the task-spooler server by `%s`\n", user_name[user_id]); } else { - snprintf(buff, 256, + snprintf(buff, 255, "Error: cannot lock the task-spooler server by UID: [%d]\n", uid); } } else { if (user_locker == uid) { - snprintf(buff, 256, + snprintf(buff, 255, "The task-spooler server has already been locked by `%s`\n", uid2user_name(uid)); } else { - snprintf(buff, 256, + snprintf(buff, 255, "Error: the task-spooler server cannot be locked by `%s`\n", uid2user_name(uid)); } @@ -1368,25 +1368,25 @@ void s_lock_server(int s, int uid) { void s_unlock_server(int s, int uid) { if (uid == 0) { user_locker = -1; - snprintf(buff, 256, "Unlock the task-spooler server by Root\n"); + snprintf(buff, 255, "Unlock the task-spooler server by Root\n"); } else { if (user_locker == uid) { int user_id = get_user_id(uid); if (user_id != -1) { user_locker = -1; - snprintf(buff, 256, "Unlock the task-spooler server by `%s`\n", + snprintf(buff, 255, "Unlock the task-spooler server by `%s`\n", user_name[user_id]); } else { - snprintf(buff, 256, + snprintf(buff, 255, "Error: cannot lock the task-spooler server by UID: [%d]\n", uid); } } else { if (user_locker == -1) { - snprintf(buff, 256, + snprintf(buff, 255, "The task-spooler server has already been unlocked\n"); } else { - snprintf(buff, 256, + snprintf(buff, 255, "Error: the task-spooler server cannot be unlocked by `%s`\n", uid2user_name(uid)); } @@ -1399,7 +1399,7 @@ void s_hold_job(int s, int jobid, int uid) { struct Job *p; p = findjob(jobid); if (p == 0) { - snprintf(buff, 256, "Error: cannot find job [%d]\n", jobid); + snprintf(buff, 255, "Error: cannot find job [%d]\n", jobid); send_list_line(s, buff); return; } @@ -1407,10 +1407,10 @@ void s_hold_job(int s, int jobid, int uid) { int job_UID = user_UID[p->user_id]; if (p->pid != 0 && (job_UID = uid || uid == 0)) { kill(p->pid, SIGSTOP); - snprintf(buff, 256, "Hold on job [%d] successfully!\n", jobid); + snprintf(buff, 255, "Hold on job [%d] successfully!\n", jobid); } else { - snprintf(buff, 256, "Error: cannot hold on job [%d]\n", jobid); + snprintf(buff, 255, "Error: cannot hold on job [%d]\n", jobid); } send_list_line(s, buff); } @@ -1420,7 +1420,7 @@ void s_restart_job(int s, int jobid, int uid) { p = findjob(jobid); if (p == 0) { - snprintf(buff, 256, "Error: cannot find job [%d]\n", jobid); + snprintf(buff, 255, "Error: cannot find job [%d]\n", jobid); send_list_line(s, buff); return; @@ -1429,9 +1429,9 @@ void s_restart_job(int s, int jobid, int uid) { int job_UID = user_UID[p->user_id]; if (p->pid != 0 && (job_UID = uid || uid == 0)) { kill(p->pid, SIGCONT); - snprintf(buff, 256, "Restart job [%d] successfully!\n", jobid); + snprintf(buff, 255, "Restart job [%d] successfully!\n", jobid); } else { - snprintf(buff, 256, "Error: cannot hold on job [%d]\n", jobid); + snprintf(buff, 255, "Error: cannot hold on job [%d]\n", jobid); } send_list_line(s, buff); } @@ -1538,9 +1538,9 @@ void s_wait_job(int s, int jobid) { if (p == 0) { if (jobid == -1) - snprintf(buff, 256, "The last job cannot be waited.\n"); + snprintf(buff, 255, "The last job cannot be waited.\n"); else - snprintf(buff, 256, "The job %i cannot be waited.\n", jobid); + snprintf(buff, 255, "The job %i cannot be waited.\n", jobid); send_list_line(s, buff); return; } @@ -1589,9 +1589,9 @@ void s_wait_running_job(int s, int jobid) { if (p == 0) { if (jobid == -1) - snprintf(buff, 256, "The last job cannot be waited.\n"); + snprintf(buff, 255, "The last job cannot be waited.\n"); else - snprintf(buff, 256, "The job %i cannot be waited.\n", jobid); + snprintf(buff, 255, "The job %i cannot be waited.\n", jobid); send_list_line(s, buff); return; } @@ -1602,11 +1602,13 @@ void s_wait_running_job(int s, int jobid) { add_to_notify_list(s, p->jobid); } -void s_set_max_slots(int new_max_slots) { +void s_set_max_slots(int s, int new_max_slots) { if (new_max_slots > 0) max_slots = new_max_slots; else warning("Received new_max_slots=%i", new_max_slots); + snprintf(buff, 255, "Reset the number of slots: %d\n", max_slots); + send_list_line(s, buff); } void s_get_max_slots(int s) { @@ -1639,9 +1641,9 @@ void s_move_urgent(int s, int jobid) { // firstjob.next means no run job if (p == 0 || firstjob.next == 0) { if (jobid == -1) - snprintf(buff, 256, "The last job cannot be urged.\n"); + snprintf(buff, 255, "The last job cannot be urged.\n"); else - snprintf(buff, 256, "The job %i cannot be urged.\n", jobid); + snprintf(buff, 255, "The job %i cannot be urged.\n", jobid); send_list_line(s, buff); return; } @@ -1649,7 +1651,7 @@ void s_move_urgent(int s, int jobid) { /* Interchange the pointers */ tmp1 = find_previous_job(p); if (tmp1 == NULL) { - snprintf(buff, 256, "The job %i cannot be urged.\n", jobid); + snprintf(buff, 255, "The job %i cannot be urged.\n", jobid); send_list_line(s, buff); return; } @@ -1668,7 +1670,7 @@ void s_swap_jobs(int s, int jobid1, int jobid2) { p2 = findjob(jobid2); if (p1 == NULL || p2 == NULL) { - snprintf(buff, 256, "The jobs %i and %i cannot be swapped.\n", jobid1, + snprintf(buff, 255, "The jobs %i and %i cannot be swapped.\n", jobid1, jobid2); send_list_line(s, buff); return; @@ -1720,9 +1722,9 @@ void s_send_state(int s, int jobid) { if (p == 0) { if (jobid == -1) - snprintf(buff, 256, "The last job cannot be stated.\n"); + snprintf(buff, 255, "The last job cannot be stated.\n"); else - snprintf(buff, 256, "The job %i cannot be stated.\n", jobid); + snprintf(buff, 255, "The job %i cannot be stated.\n", jobid); send_list_line(s, buff); return; } diff --git a/main.c b/main.c index f1c6612..f30c95c 100644 --- a/main.c +++ b/main.c @@ -813,7 +813,13 @@ int main(int argc, char **argv) { c_move_urgent(); break; case c_SET_MAX_SLOTS: - c_send_max_slots(command_line.max_slots); + if (client_uid != 0) { + printf("Reset max slots is only supported by root user!\n"); + break; + } else { + c_send_max_slots(command_line.max_slots); + c_wait_server_lines(); + } break; case c_GET_MAX_SLOTS: c_get_max_slots(); diff --git a/main.h b/main.h index 624f336..5a41373 100644 --- a/main.h +++ b/main.h @@ -352,7 +352,7 @@ void s_send_last_id(int s); void s_send_runjob(int s, int jobid); -void s_set_max_slots(int new_max_slots); +void s_set_max_slots(int s, int new_max_slots); void s_get_max_slots(int s); diff --git a/server.c b/server.c index 807ef7c..5257ce9 100644 --- a/server.c +++ b/server.c @@ -100,7 +100,7 @@ static void set_default_maxslots() { if (str != NULL) { int slots; slots = abs(atoi(str)); - s_set_max_slots(slots); + s_set_max_slots(0, slots); } } @@ -566,7 +566,9 @@ static enum Break client_read(int index) { break; case SET_MAX_SLOTS: if (m.uid == 0) - s_set_max_slots(m.u.max_slots); + s_set_max_slots(s, m.u.max_slots); + close(s); + remove_connection(index); break; case GET_MAX_SLOTS: s_get_max_slots(s); From 95c773b2a44993379ee8cec139d089883e6f9496 Mon Sep 17 00:00:00 2001 From: kylin Date: Sun, 14 Aug 2022 00:01:15 +0800 Subject: [PATCH 22/91] version 0.2.1 fixed the bug on the set max slots --- user.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/user.c b/user.c index a5eb6b1..b8dac54 100644 --- a/user.c +++ b/user.c @@ -10,6 +10,9 @@ #include "main.h" #include "user.h" +#define DEFAUL_USER_PATH "/home/kylin/task-spooler/user.txt" +#define DEFAUL_LOG_PATH "/home/kylin/task-spooler/log.txt" + void send_list_line(int s, const char *str); void error(const char *str, ...); @@ -17,7 +20,7 @@ const char *get_user_path() { char *str; str = getenv("TS_USER_PATH"); if (str == NULL || strlen(str) == 0) { - return "/home/kylin/task-spooler/user.txt"; + return DEFAUL_USER_PATH; } else { return str; } @@ -48,7 +51,7 @@ long str2int(const char *str) { const char *set_server_logfile() { logfile_path = getenv("TS_LOGFILE_PATH"); if (logfile_path == NULL || strlen(logfile_path) == 0) { - logfile_path = "/home/kylin/task-spooler/log.txt"; + logfile_path = DEFAUL_LOG_PATH; } return logfile_path; } From cc4e1487fea80e292157142cb0e6685a70757712 Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 16 Aug 2022 19:32:54 +0800 Subject: [PATCH 23/91] version 0.2.2 add the daemon control --- main.c | 14 ++++++++++++-- main.h | 4 +++- server.c | 5 +++-- server_start.c | 38 +++++++++++++++++++++++++++++--------- user.c | 6 +++--- 5 files changed, 50 insertions(+), 17 deletions(-) diff --git a/main.c b/main.c index f30c95c..3d54cd2 100644 --- a/main.c +++ b/main.c @@ -140,6 +140,7 @@ static struct option longOptions[] = { {"restart", required_argument, NULL, 0}, {"lock-ts", no_argument, NULL, 0}, {"unlock-ts", no_argument, NULL, 0}, + {"daemon", no_argument, NULL, 0}, {NULL, 0, NULL, 0}}; void parse_opts(int argc, char **argv) { @@ -160,6 +161,8 @@ void parse_opts(int argc, char **argv) { case 0: if (strcmp(longOptions[optionIdx].name, "get_logdir") == 0) { command_line.request = c_GET_LOGDIR; + } else if (strcmp(longOptions[optionIdx].name, "daemon") == 0) { + command_line.request = c_DAEMON; } else if (strcmp(longOptions[optionIdx].name, "set_logdir") == 0) { command_line.request = c_SET_LOGDIR; command_line.label = optarg; /* reuse this variable */ @@ -521,6 +524,8 @@ static void print_help(const char *cmd) { " --set_logdir [path] set the path containing log files.\n"); printf(" --plain list jobs in plain tab-separated " "texts.\n"); + printf(" --daemon Run the server as daemon by Root " + "only.\n"); printf(" --hold_job [jobid] hold on a task.\n"); printf(" --restart_job [jobid] restart a task.\n"); printf(" --lock Locker the server (Timeout: 30 " @@ -630,7 +635,11 @@ int main(int argc, char **argv) { ignore_sigpipe(); if (command_line.need_server) { - ensure_server_up(); + if (command_line.request == c_DAEMON) { + ensure_server_up(1); + } else { + ensure_server_up(0); + } c_check_version(); } @@ -643,6 +652,8 @@ int main(int argc, char **argv) { printf("Only the root can shutdown the task-spooler server\n"); } + break; + case c_DAEMON: break; case c_HOLD_JOB: c_hold_job(command_line.jobid); @@ -652,7 +663,6 @@ int main(int argc, char **argv) { case c_RESTART_JOB: c_restart_job(command_line.jobid); c_wait_server_lines(); - break; case c_LOCK_SERVER: errorlevel = c_lock_server(); diff --git a/main.h b/main.h index 5a41373..3486a66 100644 --- a/main.h +++ b/main.h @@ -63,6 +63,7 @@ enum Request { c_KILL_SERVER, c_LIST, c_LIST_ALL, + c_DAEMON, c_REFRESH_USER, c_STOP_USER, c_CONT_USER, @@ -388,7 +389,7 @@ int try_connect(int s); void wait_server_up(int fd); -int ensure_server_up(); +int ensure_server_up(int); void notify_parent(int fd); @@ -491,6 +492,7 @@ char *get_environment(); int tail_file(const char *fname, int last_lines); /* user.c */ +static const int root_UID = 0; void read_user_file(const char *path); int get_user_id(int uid); void c_refresh_user(); diff --git a/server.c b/server.c index 5257ce9..e2c356a 100644 --- a/server.c +++ b/server.c @@ -168,7 +168,6 @@ void server_main(int notify_fd, char *_path) { process_type = SERVER; max_descriptors = get_max_descriptors(); - /* Arbitrary limit, that will block the enqueuing, but should allow space * for usual ts queries */ max_jobs = max_descriptors - 5; @@ -218,7 +217,9 @@ void server_main(int notify_fd, char *_path) { initialize_log_dir(); - notify_parent(notify_fd); + if (notify_fd != 0) + notify_parent(notify_fd); + printf("Start main server loops...\n"); server_loop(ls); } diff --git a/server_start.c b/server_start.c index a471a2d..681d1e6 100644 --- a/server_start.c +++ b/server_start.c @@ -56,7 +56,7 @@ void create_socket_path(char **path) { /* Freed after preparing the socket address */ *path = (char *)malloc(size); - snprintf(*path, size - 1, "%s/socket-ts.%s", tmpdir, userid); + snprintf(*path, size, "%s/socket-ts.%s", tmpdir, userid); should_check_owner = 1; } @@ -96,6 +96,20 @@ void wait_server_up(int fd) { close(fd); } +static void server_info() { + printf("Start tast-spooler server from root[%d]\n", client_uid); + printf(" Socket path: %s [TS_SOCKET]\n", socket_path); + printf(" Read user file from %s [TS_USER_PATH]\n", get_user_path()); + printf(" Write log file to %s [TS_LOGFILE_PATH]\n\n", + set_server_logfile()); +} + +static void server_daemon() { + server_info(); + server_main(0, socket_path); + exit(0); +} + /* Returns the fd where to wait for the parent notification */ static int fork_server() { int pid; @@ -120,12 +134,7 @@ static int fork_server() { case -1: /* Error */ return -1; default: /* Parent */ - if (client_uid == 0) { - printf("Start tast-spooler server from root[%d]\n", client_uid); - printf(" Read user file from %s [TS_USER_PATH]\n", get_user_path()); - printf(" Write log file to %s [TS_LOGFILE_PATH]\n\n", - set_server_logfile()); - } + server_info(); close(p[1]); } /* Return the read fd */ @@ -138,7 +147,7 @@ void notify_parent(int fd) { close(fd); } -int ensure_server_up() { +int ensure_server_up(int daemonFlag) { int res; int notify_fd; server_socket = socket(AF_UNIX, SOCK_STREAM, 0); @@ -164,7 +173,18 @@ int ensure_server_up() { unlink(socket_path); /* Try starting the server */ - notify_fd = fork_server(); + if (client_uid == root_UID) { + if (daemonFlag) { + printf("Start task-spooler server as daemon\n"); + server_daemon(); + } else { + printf("start task-spooler server\n"); + notify_fd = fork_server(); + } + } else { + printf("only task-spooler server could be run as Root!\n"); + } + wait_server_up(notify_fd); res = try_connect(server_socket); diff --git a/user.c b/user.c index b8dac54..3061c16 100644 --- a/user.c +++ b/user.c @@ -99,9 +99,9 @@ static int find_user_by_name(const char *name) { void read_user_file(const char *path) { server_uid = getuid(); - if (server_uid != 0) { - error("the service is not run as root!"); - } + // if (server_uid != root_UID) { + // error("the service is not run as root!"); + //} FILE *fp; fp = fopen(path, "r"); if (fp == NULL) From 44b51e55e2a4e017457d795cc52b769457fb24b5 Mon Sep 17 00:00:00 2001 From: kylin Date: Sun, 21 Aug 2022 10:23:58 +0800 Subject: [PATCH 24/91] version 0.2.3 add the environment variable read and first jobid --- README.md | 15 +++++++++++++++ jobs.c | 6 ++++-- main.h | 1 + server.c | 5 +++-- user.c | 40 ++++++++++++++++++++++++++++++++++++++-- user.txt | 16 +++++++++------- 6 files changed, 70 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 04c6d83..2fdafc4 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,21 @@ Options adding jobs: -N [num] number of slots required by the job (1 default). ``` +## User configuration +using the `TS_USER_PATH` environment variable to specify the path to the user configuration. The format of the user file is shown as an example. The UID could be found by `id -u [user]`. +``` +# 1231 # comments +# TS_SLOTS = 4 # environment variable +# TS_FIRST_JOBID = 2000 # environment variable +# uid name slots +1000 Kylin 10 +3021 test1 10 +1001 test0 100 +34 user2 30 + +qweq qweq qweq # automatically skipped +``` + ## Thanks **Author** diff --git a/jobs.c b/jobs.c index 91830d6..b8ebc19 100644 --- a/jobs.c +++ b/jobs.c @@ -1607,8 +1607,10 @@ void s_set_max_slots(int s, int new_max_slots) { max_slots = new_max_slots; else warning("Received new_max_slots=%i", new_max_slots); - snprintf(buff, 255, "Reset the number of slots: %d\n", max_slots); - send_list_line(s, buff); + if (s > 0) { + snprintf(buff, 255, "Reset the number of slots: %d\n", max_slots); + send_list_line(s, buff); + } } void s_get_max_slots(int s) { diff --git a/main.h b/main.h index 3486a66..faa9ffd 100644 --- a/main.h +++ b/main.h @@ -503,6 +503,7 @@ int get_env(const char *env, int v0); long str2int(const char *str); void debug_write(const char *str); const char *uid2user_name(int uid); +int read_first_jobid_from_logfile(const char *path); /* locker */ int user_locker; diff --git a/server.c b/server.c index e2c356a..da0215b 100644 --- a/server.c +++ b/server.c @@ -204,11 +204,12 @@ void server_main(int notify_fd, char *_path) { user_queue[i] = 0; } - s_set_jobids(get_env("TS_FIRST_JOBID", 1000)); + set_server_logfile(); + int jobid = read_first_jobid_from_logfile(logfile_path); + s_set_jobids(get_env("TS_FIRST_JOBID", jobid)); jobsort_flag = get_env("TS_SORTJOBS", 0); read_user_file(get_user_path()); - set_server_logfile(); set_socket_model(_path); install_sigterm_handler(); diff --git a/user.c b/user.c index 3061c16..a8521df 100644 --- a/user.c +++ b/user.c @@ -62,7 +62,7 @@ void write_logfile(const struct Job *p) { if (f == NULL) { return; } - char buf[100]; + static char buf[100]; time_t now = time(0); strftime(buf, 100, "%Y-%m-%d %H:%M:%S", localtime(&now)); // snprintf(buf, 1024, "[%d] %s @ %s\n", p->jobid, p->command, date); @@ -97,6 +97,28 @@ static int find_user_by_name(const char *name) { } */ +int read_first_jobid_from_logfile(const char *path) { + FILE *fp; + fp = fopen(path, "r"); + if (fp == NULL) + return 1000; // default start from 1000 + char *line = NULL; + size_t len = 0; + size_t read; + int jobid; + + while ((read = getline(&line, &len, fp)) != -1) { + } + int res = sscanf(line, "[%d]", &jobid); + if (jobid <= 0 || res != 1) { + jobid = 1000; + } + + printf("last line is %s with jobid = %d\n", line, jobid); + fclose(fp); + return jobid; +} + void read_user_file(const char *path) { server_uid = getuid(); // if (server_uid != root_UID) { @@ -115,7 +137,21 @@ void read_user_file(const char *path) { while ((read = getline(&line, &len, fp)) != -1) { if (line[0] == '#') continue; - + if (strncmp("TS_SLOTS", line, 8) == 0) { + int res = sscanf(line, "TS_SLOTS = %d", &slots); + if (slots > 0 && res == 1) { + printf("TS_SLOTS = %d\n", slots); + s_set_max_slots(0, slots); + continue; + } + } else if (strncmp("TS_FIRST_JOBID", line, 14) == 0) { + int res = sscanf(line, "TS_FIRST_JOBID = %d", &slots); + if (slots > 0 && res == 1) { + printf("TS_FIRST_JOBID = %d\n", slots); + s_set_jobids(slots); + continue; + } + } int res = sscanf(line, "%d %256s %d", &UID, name, &slots); if (res != 3) { printf("error in read %s at line %s", path, line); diff --git a/user.txt b/user.txt index 9a6a6d1..ca121ed 100644 --- a/user.txt +++ b/user.txt @@ -1,8 +1,10 @@ -#uid user maxslot -1000 Kylin 10 -# 1231 -qeqe eqe q -3021 test1 10 -1001 test0 100 -34 user2 30 +# 1231 # comments +# TS_SLOTS = 4 # environment variable +# TS_FIRST_JOBID = 2000 # environment variable +# uid name slots +1000 Kylin 10 +3021 test1 10 +1001 test0 100 +34 user2 30 +qweq qweq qweq # automatically skipped From d2baf1fa3df0de90a90647e8068df7e5b900a289 Mon Sep 17 00:00:00 2001 From: kylin Date: Sun, 21 Aug 2022 11:01:29 +0800 Subject: [PATCH 25/91] version 0.2.4 remove the random output name --- client.c | 32 +++++++++---------- execute.c | 26 ++++++++++------ jobs.c | 5 +-- main.h | 4 +-- msgdump.c | 93 ++++++++++++++++++++++++++++--------------------------- server.c | 52 +++++++++++++++---------------- user.c | 4 +-- 7 files changed, 112 insertions(+), 104 deletions(-) diff --git a/client.c b/client.c index 646d859..18424ea 100644 --- a/client.c +++ b/client.c @@ -118,7 +118,7 @@ int c_wait_newjob_ok() { if (m.type != NEWJOB_OK) error("Error getting the newjob_ok"); - return m.u.jobid; + return m.jobid; } int c_wait_server_commands() { @@ -147,7 +147,7 @@ int c_wait_server_commands() { result.skipped = 1; c_send_runjob_ok(0, -1); } else - run_job(&result); + run_job(m.jobid, &result); c_end_of_job(&result); return result.errorlevel; } @@ -237,7 +237,7 @@ void c_show_info() { int res; m.type = INFO; - m.u.jobid = command_line.jobid; + m.jobid = command_line.jobid; send_msg(server_socket, &m); @@ -300,14 +300,14 @@ int c_unlock_server() { void c_hold_job(int jobid) { struct Msg m = default_msg(); m.type = HOLD_JOB; - m.u.jobid = jobid; + m.jobid = jobid; send_msg(server_socket, &m); } void c_restart_job(int jobid) { struct Msg m = default_msg(); m.type = RESTART_JOB; - m.u.jobid = jobid; + m.jobid = jobid; send_msg(server_socket, &m); } @@ -315,7 +315,7 @@ void c_stop_user(int uid) { struct Msg m = default_msg(); // int res; m.type = STOP_USER; - m.u.jobid = uid; + m.jobid = uid; send_msg(server_socket, &m); } @@ -323,7 +323,7 @@ void c_cont_user(int uid) { struct Msg m = default_msg(); // int res; m.type = CONT_USER; - m.u.jobid = uid; + m.jobid = uid; send_msg(server_socket, &m); } @@ -341,7 +341,7 @@ void c_show_last_id() { switch (m.type) { case LAST_ID: - printf("%d\n", m.u.jobid); + printf("%d\n", m.jobid); default: warning("Wrong internal message in get_output_file line size"); } @@ -423,7 +423,7 @@ static char *get_output_file(int *pid) { /* Send the request */ m.type = ASK_OUTPUT; - m.u.jobid = command_line.jobid; + m.jobid = command_line.jobid; send_msg(server_socket, &m); /* Receive the answer */ @@ -567,7 +567,7 @@ void c_remove_job() { /* Send the request */ m.type = REMOVEJOB; - m.u.jobid = command_line.jobid; + m.jobid = command_line.jobid; m.uid = client_uid; send_msg(server_socket, &m); @@ -626,7 +626,7 @@ static void c_wait_job_send() { /* Send the request */ m.type = WAITJOB; - m.u.jobid = command_line.jobid; + m.jobid = command_line.jobid; send_msg(server_socket, &m); } @@ -635,7 +635,7 @@ static void c_wait_running_job_send() { /* Send the request */ m.type = WAIT_RUNNING_JOB; - m.u.jobid = command_line.jobid; + m.jobid = command_line.jobid; send_msg(server_socket, &m); } @@ -688,7 +688,7 @@ void c_move_urgent() { /* Send the request */ m.type = URGENT; - m.u.jobid = command_line.jobid; + m.jobid = command_line.jobid; send_msg(server_socket, &m); /* Receive the answer */ @@ -722,7 +722,7 @@ void c_get_state() { /* Send the request */ m.type = GET_STATE; - m.u.jobid = command_line.jobid; + m.jobid = command_line.jobid; send_msg(server_socket, &m); /* Receive the answer */ @@ -817,7 +817,7 @@ void c_show_label() { /* Send the request */ m.type = GET_LABEL; - m.u.jobid = command_line.jobid; + m.jobid = command_line.jobid; send_msg(server_socket, &m); /* Receive the answer */ @@ -850,7 +850,7 @@ void c_show_cmd() { /* Send the request */ m.type = GET_CMD; - m.u.jobid = command_line.jobid; + m.jobid = command_line.jobid; send_msg(server_socket, &m); /* Receive the answer */ diff --git a/execute.c b/execute.c index 14e3c90..1570b3c 100644 --- a/execute.c +++ b/execute.c @@ -132,9 +132,11 @@ static void run_gzip(int fd_out, int fd_in) { } } -static void run_child(int fd_send_filename, const char *tmpdir) { +static void run_child(int fd_send_filename, const char *tmpdir, int jobid) { char *outfname; char errfname[sizeof outfname + 2]; /* .e */ + char jobid_str[100]; + int namesize; int outfd; int err; @@ -145,11 +147,13 @@ static void run_child(int fd_send_filename, const char *tmpdir) { } else if (command_line.label) { label = command_line.label; } + snprintf(jobid_str, 100, "%d", jobid); - int len_outfname = 1 + strlen(label) + strlen(".XXXXXX") + 1; + // int len_outfname = 1 + strlen(label) + strlen(".XXXXXX") + 1; + int len_outfname = 3 + strlen(label) + strlen(jobid_str); outfname = malloc(len_outfname); - snprintf(outfname, len_outfname, "/%s.XXXXXX", label); + snprintf(outfname, len_outfname, "/%s.%d", label, jobid); if (command_line.store_output) { /* Prepare path */ @@ -173,7 +177,8 @@ static void run_child(int fd_send_filename, const char *tmpdir) { /* gzip output goes to the filename */ /* This will be the handle other than 0,1,2 */ /* mkstemp doesn't admit adding ".gz" to the pattern */ - outfd = mkstemp(outfname_full); /* stdout */ + // outfd = mkstemp(outfname_full); /* stdout */ + outfd = open(outfname_full, O_CREAT | O_WRONLY | O_TRUNC, 0644); assert(outfd != -1); /* Program stdout and stderr */ @@ -184,7 +189,7 @@ static void run_child(int fd_send_filename, const char *tmpdir) { int errfd; strncpy(errfname, outfname_full, sizeof errfname); strncat(errfname, ".e", 2 + 1); - errfd = open(errfname, O_CREAT | O_WRONLY | O_TRUNC, 0600); + errfd = open(errfname, O_CREAT | O_WRONLY | O_TRUNC, 0644); assert(err == 0); err = dup2(errfd, 2); assert(err == 0); @@ -203,13 +208,14 @@ static void run_child(int fd_send_filename, const char *tmpdir) { run_gzip(outfd, p[0]); } else { /* Prepare the filename */ - outfd = mkstemp(outfname_full); /* stdout */ - dup2(outfd, 1); /* stdout */ + // outfd = mkstemp(outfname_full); /* stdout */ + outfd = open(outfname_full, O_CREAT | O_WRONLY | O_TRUNC, 0644); + dup2(outfd, 1); /* stdout */ if (command_line.stderr_apart) { int errfd; strncpy(errfname, outfname_full, sizeof errfname); strncat(errfname, ".e", 2 + 1); - errfd = open(errfname, O_CREAT | O_WRONLY | O_TRUNC, 0600); + errfd = open(errfname, O_CREAT | O_WRONLY | O_TRUNC, 0644); dup2(errfd, 2); close(errfd); } else @@ -238,7 +244,7 @@ static void run_child(int fd_send_filename, const char *tmpdir) { execvp(command_line.command.array[0], command_line.command.array); } -int run_job(struct Result *res) { +int run_job(int jobid, struct Result *res) { int pid; int errorlevel; int p[2]; @@ -262,7 +268,7 @@ int run_job(struct Result *res) { restore_sigmask(); close(server_socket); close(p[0]); - run_child(p[1], path); + run_child(p[1], path, jobid); /* Not reachable, if the 'exec' of the command * works. Thus, command exists, etc. */ fprintf(stderr, "ts could not run the command\n"); diff --git a/jobs.c b/jobs.c index b8ebc19..cea9194 100644 --- a/jobs.c +++ b/jobs.c @@ -930,6 +930,7 @@ void s_send_runjob(int s, int jobid) { * (-nf?) . */ m.u.last_errorlevel = p->dependency_errorlevel; + m.jobid = jobid; send_msg(s, &m); } @@ -1009,7 +1010,7 @@ void s_send_last_id(int s) { struct Msg m = default_msg(); m.type = LAST_ID; - m.u.jobid = jobids - 1; + m.jobid = jobids - 1; send_msg(s, &m); } @@ -1254,7 +1255,7 @@ int s_remove_job(int s, int *jobid, int client_uid) { notify_errorlevel(p); /* Notify the clients in wait_job */ - check_notify_list(m.u.jobid); + check_notify_list(m.jobid); /* Update the list pointers */ before_p->next = p->next; diff --git a/main.h b/main.h index faa9ffd..9015dc5 100644 --- a/main.h +++ b/main.h @@ -140,6 +140,7 @@ enum Jobstate { QUEUED, RUNNING, FINISHED, SKIPPED, HOLDING_CLIENT }; struct Msg { enum MsgTypes type; int uid; + int jobid; union { struct { int command_size; @@ -156,7 +157,6 @@ struct Msg { int store_output; int pid; } output; - int jobid; struct Result { int errorlevel; int died_by_signal; @@ -396,7 +396,7 @@ void notify_parent(int fd); void create_socket_path(char **path); /* execute.c */ -int run_job(struct Result *res); +int run_job(int jobid, struct Result *res); /* client_run.c */ void c_run_tail(const char *filename); diff --git a/msgdump.c b/msgdump.c index 398bbcb..76d2f99 100644 --- a/msgdump.c +++ b/msgdump.c @@ -6,53 +6,54 @@ */ #include #include + #include "main.h" void msgdump(FILE *f, const struct Msg *m) { - fprintf(f, "msgdump:\n"); - switch (m->type) { - case KILL_SERVER: - fprintf(f, " KILL SERVER\n"); - break; - case NEWJOB: - fprintf(f, " NEWJOB\n"); - fprintf(f, " Commandsize: %i\n", m->u.newjob.command_size); - break; - case NEWJOB_OK: - fprintf(f, " NEWJOB_OK\n"); - fprintf(f, " JobID: '%i'\n", m->u.jobid); - break; - case RUNJOB: - fprintf(f, " RUNJOB\n"); - break; - case RUNJOB_OK: - fprintf(f, " RUNJOB_OK\n"); - fprintf(f, " Outputsize: %i\n", m->u.output.ofilename_size); - fprintf(f, " pid: %i\n", m->u.output.pid); - break; - case ENDJOB: - fprintf(f, " ENDJOB\n"); - break; - case LIST: - fprintf(f, " LIST\n"); - break; - case LIST_LINE: - fprintf(f, " LIST_LINE\n"); - fprintf(f, " Linesize: %i\n", m->u.size); - break; - case ASK_OUTPUT: - fprintf(f, " ASK_OUTPUT\n"); - fprintf(f, " Jobid: %i\n", m->u.jobid); - break; - case ANSWER_OUTPUT: - fprintf(f, " ANSWER_OUTPUT\n"); - fprintf(f, " Outputsize: %i\n", m->u.output.ofilename_size); - fprintf(f, " PID: %i\n", m->u.output.pid); - break; - case GET_LABEL: - fprintf(f, "GET_LABEL\n"); - fprintf(f, " Jobid: %i\n", m->u.jobid); - default: - fprintf(f, " Unknown message: %i\n", m->type); - } + fprintf(f, "msgdump:\n"); + switch (m->type) { + case KILL_SERVER: + fprintf(f, " KILL SERVER\n"); + break; + case NEWJOB: + fprintf(f, " NEWJOB\n"); + fprintf(f, " Commandsize: %i\n", m->u.newjob.command_size); + break; + case NEWJOB_OK: + fprintf(f, " NEWJOB_OK\n"); + fprintf(f, " JobID: '%i'\n", m->jobid); + break; + case RUNJOB: + fprintf(f, " RUNJOB\n"); + break; + case RUNJOB_OK: + fprintf(f, " RUNJOB_OK\n"); + fprintf(f, " Outputsize: %i\n", m->u.output.ofilename_size); + fprintf(f, " pid: %i\n", m->u.output.pid); + break; + case ENDJOB: + fprintf(f, " ENDJOB\n"); + break; + case LIST: + fprintf(f, " LIST\n"); + break; + case LIST_LINE: + fprintf(f, " LIST_LINE\n"); + fprintf(f, " Linesize: %i\n", m->u.size); + break; + case ASK_OUTPUT: + fprintf(f, " ASK_OUTPUT\n"); + fprintf(f, " Jobid: %i\n", m->jobid); + break; + case ANSWER_OUTPUT: + fprintf(f, " ANSWER_OUTPUT\n"); + fprintf(f, " Outputsize: %i\n", m->u.output.ofilename_size); + fprintf(f, " PID: %i\n", m->u.output.pid); + break; + case GET_LABEL: + fprintf(f, "GET_LABEL\n"); + fprintf(f, " Jobid: %i\n", m->jobid); + default: + fprintf(f, " Unknown message: %i\n", m->type); + } } diff --git a/server.c b/server.c index da0215b..9a99519 100644 --- a/server.c +++ b/server.c @@ -392,28 +392,28 @@ static enum Break client_read(int index) { remove_connection(index); break; case HOLD_JOB: - s_hold_job(s, m.u.jobid, m.uid); + s_hold_job(s, m.jobid, m.uid); close(s); remove_connection(index); break; case RESTART_JOB: - s_restart_job(s, m.u.jobid, m.uid); + s_restart_job(s, m.jobid, m.uid); close(s); remove_connection(index); break; case STOP_USER: if (m.uid == getuid()) { - if (m.u.jobid != 0) { - s_stop_user(s, m.u.jobid); + if (m.jobid != 0) { + s_stop_user(s, m.jobid); s_user_status_all(s); } else { s_stop_all_users(s); - s_user_status(s, get_user_id(m.u.jobid)); + s_user_status(s, get_user_id(m.jobid)); } } else { - if (m.uid == m.u.jobid) { - s_stop_user(s, m.u.jobid); - s_user_status(s, get_user_id(m.u.jobid)); + if (m.uid == m.jobid) { + s_stop_user(s, m.jobid); + s_user_status(s, get_user_id(m.jobid)); } } close(s); @@ -421,17 +421,17 @@ static enum Break client_read(int index) { break; case CONT_USER: if (m.uid == getuid()) { - if (m.u.jobid != 0) { - s_cont_user(s, m.u.jobid); + if (m.jobid != 0) { + s_cont_user(s, m.jobid); s_user_status_all(s); } else { s_cont_all_users(s); - s_user_status(s, get_user_id(m.u.jobid)); + s_user_status(s, get_user_id(m.jobid)); } } else { - if (m.uid == m.u.jobid) { - s_cont_user(s, m.u.jobid); - s_user_status(s, get_user_id(m.u.jobid)); + if (m.uid == m.jobid) { + s_cont_user(s, m.jobid); + s_user_status(s, get_user_id(m.jobid)); } } close(s); @@ -495,7 +495,7 @@ static enum Break client_read(int index) { remove_connection(index); break; case INFO: - s_job_info(s, m.u.jobid); + s_job_info(s, m.jobid); close(s); remove_connection(index); break; @@ -503,10 +503,10 @@ static enum Break client_read(int index) { s_send_last_id(s); break; case GET_LABEL: - s_get_label(s, m.u.jobid); + s_get_label(s, m.jobid); break; case GET_CMD: - s_send_cmd(s, m.u.jobid); + s_send_cmd(s, m.jobid); break; case ENDJOB: job_finished(&m.u.result, client_cs[index].jobid); @@ -526,16 +526,16 @@ static enum Break client_read(int index) { } break; case ASK_OUTPUT: - s_send_output(s, m.u.jobid); + s_send_output(s, m.jobid); break; case REMOVEJOB: { int went_ok; /* Will update the jobid. If it's -1, will set the jobid found */ - went_ok = s_remove_job(s, &m.u.jobid, m.uid); + went_ok = s_remove_job(s, &m.jobid, m.uid); if (went_ok) { int i; for (i = 0; i < nconnections; ++i) { - if (client_cs[i].hasjob && client_cs[i].jobid == m.u.jobid) { + if (client_cs[i].hasjob && client_cs[i].jobid == m.jobid) { close(client_cs[i].socket); /* So remove_connection doesn't call s_removejob again */ @@ -549,17 +549,17 @@ static enum Break client_read(int index) { } } break; case WAITJOB: - s_wait_job(s, m.u.jobid); + s_wait_job(s, m.jobid); break; case WAIT_RUNNING_JOB: - s_wait_running_job(s, m.u.jobid); + s_wait_running_job(s, m.jobid); break; case COUNT_RUNNING: s_count_running_jobs(s); break; case URGENT: - if (m.uid == 0 || m.uid == s_get_job_uid(m.u.jobid)) { - s_move_urgent(s, m.u.jobid); + if (m.uid == 0 || m.uid == s_get_job_uid(m.jobid)) { + s_move_urgent(s, m.jobid); } if (jobsort_flag) s_sort_jobs(); @@ -591,7 +591,7 @@ static enum Break client_read(int index) { remove_connection(index); break; case GET_STATE: - s_send_state(s, m.u.jobid); + s_send_state(s, m.jobid); break; case GET_ENV: s_get_env(s, m.u.size); @@ -648,7 +648,7 @@ static void s_newjob_ok(int index) { s = client_cs[index].socket; m.type = NEWJOB_OK; - m.u.jobid = client_cs[index].jobid; + m.jobid = client_cs[index].jobid; send_msg(s, &m); } diff --git a/user.c b/user.c index a8521df..4366cb7 100644 --- a/user.c +++ b/user.c @@ -111,12 +111,12 @@ int read_first_jobid_from_logfile(const char *path) { } int res = sscanf(line, "[%d]", &jobid); if (jobid <= 0 || res != 1) { - jobid = 1000; + jobid = 999; } printf("last line is %s with jobid = %d\n", line, jobid); fclose(fp); - return jobid; + return jobid + 1; } void read_user_file(const char *path) { From 3f1d82f0f7ea7e5249dcb5bc8e092c2d06f29528 Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 18 Oct 2022 10:26:43 +0800 Subject: [PATCH 26/91] fixed the typo --- jobs.c | 3 ++- main.c | 2 +- user.txt | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/jobs.c b/jobs.c index cea9194..2d9f8cf 100644 --- a/jobs.c +++ b/jobs.c @@ -735,8 +735,9 @@ int next_run_job() { int num_slots = p->num_slots, id = p->user_id; if (id == uid && free_slots >= num_slots && user_max_slots[id] - user_busy[id] >= num_slots) { - busy_slots = busy_slots + num_slots; + busy_slots += num_slots; user_busy[id] += num_slots; + user_jobs[id]++; user_queue[id]--; return p->jobid; diff --git a/main.c b/main.c index 3d54cd2..cd5a235 100644 --- a/main.c +++ b/main.c @@ -529,7 +529,7 @@ static void print_help(const char *cmd) { printf(" --hold_job [jobid] hold on a task.\n"); printf(" --restart_job [jobid] restart a task.\n"); printf(" --lock Locker the server (Timeout: 30 " - "sec.)git " + "sec.)" "For Root, timeout is infinity.\n"); printf(" --unlock Unlocker the server.\n"); printf(" --stop [user] For normal user, pause all " diff --git a/user.txt b/user.txt index ca121ed..3c116f2 100644 --- a/user.txt +++ b/user.txt @@ -1,5 +1,5 @@ # 1231 # comments -# TS_SLOTS = 4 # environment variable +TS_SLOTS = 4 # environment variable # TS_FIRST_JOBID = 2000 # environment variable # uid name slots 1000 Kylin 10 From 890db2446c8c50ab8d861450ab1e3a8a8ef3496c Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 18 Oct 2022 14:31:36 +0800 Subject: [PATCH 27/91] fixed bug for the running job remove --- client.c | 4 ++++ jobs.c | 6 +++--- main.c | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/client.c b/client.c index 18424ea..8313220 100644 --- a/client.c +++ b/client.c @@ -518,6 +518,7 @@ void c_kill_job() { exit(-1); } + printf("kill the pid: %d\n", pid); /* Send SIGTERM to the process group, as pid is for process group */ kill(-pid, SIGTERM); } @@ -583,6 +584,9 @@ void c_remove_job() { string = (char *)malloc(m.u.size); res = recv_bytes(server_socket, string, m.u.size); fprintf(stderr, "Error in the request: %s", string); + if (strncmp(string, "Running job", 11) == 0) { + c_kill_job(); + } free(string); exit(-1); /* WILL NOT GO FURTHER */ diff --git a/jobs.c b/jobs.c index 2d9f8cf..287f466 100644 --- a/jobs.c +++ b/jobs.c @@ -1227,11 +1227,11 @@ int s_remove_job(int s, int *jobid, int client_uid) { if (p->state == RUNNING) { if (p->pid != 0 && (user_UID[p->user_id] == client_uid)) { - kill(p->pid, SIGTERM); + kill(p->pid, SIGKILL); if (*jobid == -1) - snprintf(buff, 255, "The last job is removed.\n"); + snprintf(buff, 255, "Running job of last job is removed.\n"); else - snprintf(buff, 255, "The job %i is removed.\n", *jobid); + snprintf(buff, 255, "Running job %i[%d] is removed.\n", *jobid, p->pid); send_list_line(s, buff); return 0; } diff --git a/main.c b/main.c index cd5a235..0f09e61 100644 --- a/main.c +++ b/main.c @@ -810,6 +810,7 @@ int main(int argc, char **argv) { case c_REMOVEJOB: if (!command_line.need_server) error("The command %i needs the server", command_line.request); + printf("remove the job\n"); c_remove_job(); break; case c_WAITJOB: From 5d765a314f80343277a724198e883f2075189f34 Mon Sep 17 00:00:00 2001 From: kylin Date: Sat, 22 Oct 2022 22:05:26 +0800 Subject: [PATCH 28/91] fixed typo for hold and restart --- main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.c b/main.c index 0f09e61..9ec0bba 100644 --- a/main.c +++ b/main.c @@ -526,8 +526,8 @@ static void print_help(const char *cmd) { "texts.\n"); printf(" --daemon Run the server as daemon by Root " "only.\n"); - printf(" --hold_job [jobid] hold on a task.\n"); - printf(" --restart_job [jobid] restart a task.\n"); + printf(" --hold [jobid] hold on a task.\n"); + printf(" --restart [jobid] rerun a hold task.\n"); printf(" --lock Locker the server (Timeout: 30 " "sec.)" "For Root, timeout is infinity.\n"); From 544a5dd031b62e488c4b145558e114e0f46c3b03 Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 8 Nov 2022 15:04:20 +0800 Subject: [PATCH 29/91] fix the bug on kill the subprocessors --- client.c | 2 +- jobs.c | 26 +++++++++++++++++++++----- kill_ppid.sh | 47 +++++++++++++++++++++++++++++++++++++++++++++++ user.h | 1 + 4 files changed, 70 insertions(+), 6 deletions(-) create mode 100755 kill_ppid.sh diff --git a/client.c b/client.c index 8313220..04dc96b 100644 --- a/client.c +++ b/client.c @@ -585,7 +585,7 @@ void c_remove_job() { res = recv_bytes(server_socket, string, m.u.size); fprintf(stderr, "Error in the request: %s", string); if (strncmp(string, "Running job", 11) == 0) { - c_kill_job(); + ; // c_kill_job(); } free(string); exit(-1); diff --git a/jobs.c b/jobs.c index 287f466..cf16f72 100644 --- a/jobs.c +++ b/jobs.c @@ -50,6 +50,16 @@ void notify_errorlevel(struct Job *p); void s_set_jobids(int i) { jobids = i; } +static void kill_pid(int ppid, const char *signal) { + FILE *fp; + char command[1024]; + sprintf(command, GET_PID " %d %s", ppid, signal); + + fp = popen(command, "r"); + + pclose(fp); +} + static void destroy_job(struct Job *p) { free(p->notify_errorlevel_to); free(p->command); @@ -1045,7 +1055,8 @@ void s_cont_user(int s, int uid) { if (p->user_id == user_id && p->state == RUNNING) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { - kill(p->pid, SIGCONT); + // kill(p->pid, SIGCONT); + kill_pid(p->pid, "-cont"); } } p = p->next; @@ -1068,7 +1079,8 @@ void s_stop_user(int s, int uid) { if (p->user_id == user_id && p->state == RUNNING) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { - kill(p->pid, SIGSTOP); + // kill(p->pid, SIGSTOP); + kill_pid(p->pid, "-stop"); } else { char *label = "(...)"; if (p->label != NULL) @@ -1227,7 +1239,9 @@ int s_remove_job(int s, int *jobid, int client_uid) { if (p->state == RUNNING) { if (p->pid != 0 && (user_UID[p->user_id] == client_uid)) { - kill(p->pid, SIGKILL); + // kill((p->pid), SIGTERM); + kill_pid(p->pid, "-9"); + if (*jobid == -1) snprintf(buff, 255, "Running job of last job is removed.\n"); else @@ -1408,7 +1422,8 @@ void s_hold_job(int s, int jobid, int uid) { int job_UID = user_UID[p->user_id]; if (p->pid != 0 && (job_UID = uid || uid == 0)) { - kill(p->pid, SIGSTOP); + // kill(p->pid, SIGSTOP); + kill_pid(p->pid, "-stop"); snprintf(buff, 255, "Hold on job [%d] successfully!\n", jobid); } else { @@ -1430,7 +1445,8 @@ void s_restart_job(int s, int jobid, int uid) { int job_UID = user_UID[p->user_id]; if (p->pid != 0 && (job_UID = uid || uid == 0)) { - kill(p->pid, SIGCONT); + // kill(p->pid, SIGCONT); + kill_pid(p->pid, "-cont"); snprintf(buff, 255, "Restart job [%d] successfully!\n", jobid); } else { snprintf(buff, 255, "Error: cannot hold on job [%d]\n", jobid); diff --git a/kill_ppid.sh b/kill_ppid.sh new file mode 100755 index 0000000..27eede6 --- /dev/null +++ b/kill_ppid.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# getting children generally resolves nicely at some point +get_child() { + echo $(pgrep -laP $1 | awk '{print $1}') +} + +# recursively getting parents isn't that useful, +# so single use function +get_parent() { + echo $(ps -o ppid= -p 36700) +} + +get_children() { + __RET=$(get_child $1) + __CHILDREN= + while [ -n "$__RET" ]; do + __CHILDREN+="$__RET " + __RET=$(get_child $__RET) + done + + __CHILDREN=$(echo "${__CHILDREN}" | xargs | sort) + + echo "${__CHILDREN}" +} + +pids=`get_children $1` +for pid in ${pids}; +do + if [ -n $2 ] + then + # echo ${pid} $2 >> pid.txt + kill $2 ${pid} + else + echo ${pid} + fi +done + +if [ -n $2 ] +then + # echo PPID= $1 $2 >> pid.txt + kill $2 $1 +else + echo PPID= $1 +fi + + diff --git a/user.h b/user.h index ecfce36..f7157ab 100644 --- a/user.h +++ b/user.h @@ -1,5 +1,6 @@ #define USER_NAME_WIDTH 256 #define USER_MAX 100 +#define GET_PID "bash /home/kylin/task-spooler/kill_ppid.sh" char user_name[USER_MAX][USER_NAME_WIDTH]; int server_uid; From ab056d18088b714d5d8745fd0664022d4568bdb0 Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 8 Nov 2022 15:17:33 +0800 Subject: [PATCH 30/91] fix the bug on kill the subprocessors --- main.h | 2 ++ server_start.c | 4 ++-- user.h | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/main.h b/main.h index 9015dc5..8644972 100644 --- a/main.h +++ b/main.h @@ -4,6 +4,8 @@ Please find the license in the provided COPYING file. */ +#define GET_PID "bash /home/kylin/task-spooler/kill_ppid.sh" + enum { CMD_LEN = 500, PROTOCOL_VERSION = 730 }; enum MsgTypes { diff --git a/server_start.c b/server_start.c index 681d1e6..a6d747d 100644 --- a/server_start.c +++ b/server_start.c @@ -100,8 +100,8 @@ static void server_info() { printf("Start tast-spooler server from root[%d]\n", client_uid); printf(" Socket path: %s [TS_SOCKET]\n", socket_path); printf(" Read user file from %s [TS_USER_PATH]\n", get_user_path()); - printf(" Write log file to %s [TS_LOGFILE_PATH]\n\n", - set_server_logfile()); + printf(" Write log file to %s [TS_LOGFILE_PATH]\n", set_server_logfile()); + printf(" Extra Bash CMD from `%s`\n\n", GET_PID); } static void server_daemon() { diff --git a/user.h b/user.h index f7157ab..ecfce36 100644 --- a/user.h +++ b/user.h @@ -1,6 +1,5 @@ #define USER_NAME_WIDTH 256 #define USER_MAX 100 -#define GET_PID "bash /home/kylin/task-spooler/kill_ppid.sh" char user_name[USER_MAX][USER_NAME_WIDTH]; int server_uid; From 14db6a9d34044366acfc2f029a1e8e1b0f7587a3 Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 8 Nov 2022 16:02:11 +0800 Subject: [PATCH 31/91] add the sudo to kill in bash file --- kill_ppid.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kill_ppid.sh b/kill_ppid.sh index 27eede6..fc82738 100755 --- a/kill_ppid.sh +++ b/kill_ppid.sh @@ -30,7 +30,7 @@ do if [ -n $2 ] then # echo ${pid} $2 >> pid.txt - kill $2 ${pid} + sudo kill $2 ${pid} else echo ${pid} fi @@ -39,7 +39,7 @@ done if [ -n $2 ] then # echo PPID= $1 $2 >> pid.txt - kill $2 $1 + sudo kill $2 $1 else echo PPID= $1 fi From 25002b495a172aa8ad42a90d0b2edc24d0d4f18c Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 8 Nov 2022 21:55:54 +0800 Subject: [PATCH 32/91] fix the potential bugs --- client.c | 58 +++++++++++++++++++++++++++++++++++++++++--------------- jobs.c | 16 +++------------- main.h | 1 + user.c | 12 +++++++++++- 4 files changed, 58 insertions(+), 29 deletions(-) diff --git a/client.c b/client.c index 04dc96b..ec575a5 100644 --- a/client.c +++ b/client.c @@ -297,20 +297,6 @@ int c_unlock_server() { return error_sig; } -void c_hold_job(int jobid) { - struct Msg m = default_msg(); - m.type = HOLD_JOB; - m.jobid = jobid; - send_msg(server_socket, &m); -} - -void c_restart_job(int jobid) { - struct Msg m = default_msg(); - m.type = RESTART_JOB; - m.jobid = jobid; - send_msg(server_socket, &m); -} - void c_stop_user(int uid) { struct Msg m = default_msg(); // int res; @@ -461,6 +447,48 @@ static char *get_output_file(int *pid) { return 0; } +void c_hold_job(int jobid) { + int pid = 0; + /* This will exit if there is any error */ + get_output_file(&pid); + + if (pid == -1 || pid == 0) { + fprintf(stderr, "Error: strange PID received: %i\n", pid); + exit(-1); + } + + // printf("kill the pid: %d\n", pid); + /* Send SIGTERM to the process group, as pid is for process group */ + // kill(-pid, SIGSTOP); + kill_pid(-pid, "-stop"); + + struct Msg m = default_msg(); + m.type = HOLD_JOB; + m.jobid = jobid; + send_msg(server_socket, &m); +} + +void c_restart_job(int jobid) { + int pid = 0; + /* This will exit if there is any error */ + get_output_file(&pid); + + if (pid == -1 || pid == 0) { + fprintf(stderr, "Error: strange PID received: %i\n", pid); + exit(-1); + } + + // printf("kill the pid: %d\n", pid); + /* Send SIGTERM to the process group, as pid is for process group */ + // kill(-pid, SIGCONT); + kill_pid(-pid, "-cont"); + + struct Msg m = default_msg(); + m.type = RESTART_JOB; + m.jobid = jobid; + send_msg(server_socket, &m); +} + int c_tail() { char *str; int pid; @@ -585,7 +613,7 @@ void c_remove_job() { res = recv_bytes(server_socket, string, m.u.size); fprintf(stderr, "Error in the request: %s", string); if (strncmp(string, "Running job", 11) == 0) { - ; // c_kill_job(); + c_kill_job(); } free(string); exit(-1); diff --git a/jobs.c b/jobs.c index cf16f72..af6228b 100644 --- a/jobs.c +++ b/jobs.c @@ -50,16 +50,6 @@ void notify_errorlevel(struct Job *p); void s_set_jobids(int i) { jobids = i; } -static void kill_pid(int ppid, const char *signal) { - FILE *fp; - char command[1024]; - sprintf(command, GET_PID " %d %s", ppid, signal); - - fp = popen(command, "r"); - - pclose(fp); -} - static void destroy_job(struct Job *p) { free(p->notify_errorlevel_to); free(p->command); @@ -1240,7 +1230,7 @@ int s_remove_job(int s, int *jobid, int client_uid) { if (p->state == RUNNING) { if (p->pid != 0 && (user_UID[p->user_id] == client_uid)) { // kill((p->pid), SIGTERM); - kill_pid(p->pid, "-9"); + // kill_pid(p->pid, "-9"); if (*jobid == -1) snprintf(buff, 255, "Running job of last job is removed.\n"); @@ -1423,7 +1413,7 @@ void s_hold_job(int s, int jobid, int uid) { int job_UID = user_UID[p->user_id]; if (p->pid != 0 && (job_UID = uid || uid == 0)) { // kill(p->pid, SIGSTOP); - kill_pid(p->pid, "-stop"); + // kill_pid(p->pid, "-stop"); snprintf(buff, 255, "Hold on job [%d] successfully!\n", jobid); } else { @@ -1446,7 +1436,7 @@ void s_restart_job(int s, int jobid, int uid) { int job_UID = user_UID[p->user_id]; if (p->pid != 0 && (job_UID = uid || uid == 0)) { // kill(p->pid, SIGCONT); - kill_pid(p->pid, "-cont"); + // kill_pid(p->pid, "-cont"); snprintf(buff, 255, "Restart job [%d] successfully!\n", jobid); } else { snprintf(buff, 255, "Error: cannot hold on job [%d]\n", jobid); diff --git a/main.h b/main.h index 8644972..b074fc6 100644 --- a/main.h +++ b/main.h @@ -506,6 +506,7 @@ long str2int(const char *str); void debug_write(const char *str); const char *uid2user_name(int uid); int read_first_jobid_from_logfile(const char *path); +void kill_pid(int ppid, const char *signal); /* locker */ int user_locker; diff --git a/user.c b/user.c index 4366cb7..87c367d 100644 --- a/user.c +++ b/user.c @@ -223,4 +223,14 @@ int get_user_id(int uid) { } } return -1; -} \ No newline at end of file +} + +void kill_pid(int ppid, const char *signal) { + FILE *fp; + char command[1024]; + sprintf(command, GET_PID " %d %s", ppid, signal); + + fp = popen(command, "r"); + + pclose(fp); +} From 31ed211645dd6974895ef377d71922465d3a5e83 Mon Sep 17 00:00:00 2001 From: kylin Date: Wed, 9 Nov 2022 08:15:37 +0800 Subject: [PATCH 33/91] fix the bugs for sudo --- client.c | 4 ++-- kill_ppid.sh | 40 +++++++++++++++++++++++++++------------- user.c | 2 +- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/client.c b/client.c index ec575a5..71105f2 100644 --- a/client.c +++ b/client.c @@ -460,7 +460,7 @@ void c_hold_job(int jobid) { // printf("kill the pid: %d\n", pid); /* Send SIGTERM to the process group, as pid is for process group */ // kill(-pid, SIGSTOP); - kill_pid(-pid, "-stop"); + kill_pid(pid, "-stop"); struct Msg m = default_msg(); m.type = HOLD_JOB; @@ -481,7 +481,7 @@ void c_restart_job(int jobid) { // printf("kill the pid: %d\n", pid); /* Send SIGTERM to the process group, as pid is for process group */ // kill(-pid, SIGCONT); - kill_pid(-pid, "-cont"); + kill_pid(pid, "-cont"); struct Msg m = default_msg(); m.type = RESTART_JOB; diff --git a/kill_ppid.sh b/kill_ppid.sh index fc82738..3d8adce 100755 --- a/kill_ppid.sh +++ b/kill_ppid.sh @@ -5,12 +5,6 @@ get_child() { echo $(pgrep -laP $1 | awk '{print $1}') } -# recursively getting parents isn't that useful, -# so single use function -get_parent() { - echo $(ps -o ppid= -p 36700) -} - get_children() { __RET=$(get_child $1) __CHILDREN= @@ -24,24 +18,44 @@ get_children() { echo "${__CHILDREN}" } +if [ 1 -gt $# ]; +then + echo "not input PID" + exit 1 +fi + +owner=`ps -o user= -p $1` +if [ -z "$owner" ]; +then + # echo "not a valid PID" + exit 1 +fi pids=`get_children $1` + +user=`whoami` + +extra="" +# echo $owner $user +if [[ "$owner" != "$user" ]]; then + extra="sudo" +fi + for pid in ${pids}; do if [ -n $2 ] then - # echo ${pid} $2 >> pid.txt - sudo kill $2 ${pid} - else - echo ${pid} + # echo ${extra} ${pid} $2 + ${extra} kill $2 ${pid} fi done if [ -n $2 ] then - # echo PPID= $1 $2 >> pid.txt - sudo kill $2 $1 + # echo PPID= $1 ${extra} $2 + ${extra} kill $2 $1 else - echo PPID= $1 + echo ${extra} PPID= $1 fi + diff --git a/user.c b/user.c index 87c367d..2d957a2 100644 --- a/user.c +++ b/user.c @@ -229,7 +229,7 @@ void kill_pid(int ppid, const char *signal) { FILE *fp; char command[1024]; sprintf(command, GET_PID " %d %s", ppid, signal); - + // printf("command = %s\n", command); fp = popen(command, "r"); pclose(fp); From cd57723095ed2dbff38c210b33428cd3afac5c9c Mon Sep 17 00:00:00 2001 From: kylin Date: Wed, 9 Nov 2022 08:35:58 +0800 Subject: [PATCH 34/91] fix the bugs for kill signal --- client.c | 4 ++-- jobs.c | 4 ++-- kill_ppid.sh | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client.c b/client.c index 71105f2..44cce4c 100644 --- a/client.c +++ b/client.c @@ -460,7 +460,7 @@ void c_hold_job(int jobid) { // printf("kill the pid: %d\n", pid); /* Send SIGTERM to the process group, as pid is for process group */ // kill(-pid, SIGSTOP); - kill_pid(pid, "-stop"); + kill_pid(pid, "STOP"); struct Msg m = default_msg(); m.type = HOLD_JOB; @@ -481,7 +481,7 @@ void c_restart_job(int jobid) { // printf("kill the pid: %d\n", pid); /* Send SIGTERM to the process group, as pid is for process group */ // kill(-pid, SIGCONT); - kill_pid(pid, "-cont"); + kill_pid(pid, "CONT"); struct Msg m = default_msg(); m.type = RESTART_JOB; diff --git a/jobs.c b/jobs.c index af6228b..408ea74 100644 --- a/jobs.c +++ b/jobs.c @@ -1046,7 +1046,7 @@ void s_cont_user(int s, int uid) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { // kill(p->pid, SIGCONT); - kill_pid(p->pid, "-cont"); + kill_pid(p->pid, "CONT"); } } p = p->next; @@ -1070,7 +1070,7 @@ void s_stop_user(int s, int uid) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { // kill(p->pid, SIGSTOP); - kill_pid(p->pid, "-stop"); + kill_pid(p->pid, "STOP"); } else { char *label = "(...)"; if (p->label != NULL) diff --git a/kill_ppid.sh b/kill_ppid.sh index 3d8adce..54d5c79 100755 --- a/kill_ppid.sh +++ b/kill_ppid.sh @@ -45,14 +45,14 @@ do if [ -n $2 ] then # echo ${extra} ${pid} $2 - ${extra} kill $2 ${pid} + ${extra} kill -s $2 ${pid} fi done if [ -n $2 ] then # echo PPID= $1 ${extra} $2 - ${extra} kill $2 $1 + ${extra} kill -s $2 $1 else echo ${extra} PPID= $1 fi From 9ef385275b4a270ef0a029a86936b4ae86309dd9 Mon Sep 17 00:00:00 2001 From: kylin Date: Mon, 14 Nov 2022 22:46:22 +0800 Subject: [PATCH 35/91] remove the extra depends on bash file --- Makefile | 2 +- jobs.c | 2 ++ main.h | 2 +- server_start.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++- user.c | 13 +++++--- user.h | 1 + 6 files changed, 101 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 7014454..c43e461 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ PREFIX?=/usr/local PREFIX_LOCAL=~ GLIBCFLAGS=-D_XOPEN_SOURCE=500 -D__STRICT_ANSI__ CPPFLAGS+=$(GLIBCFLAGS) -CFLAGS?=-pedantic -ansi -Wall -g -O0 -std=c11 +CFLAGS?=-pedantic -ansi -Wall -g -O0 -std=gnu11 OBJECTS=main.o \ server.o \ server_start.o \ diff --git a/jobs.c b/jobs.c index 408ea74..e55c7b2 100644 --- a/jobs.c +++ b/jobs.c @@ -1039,6 +1039,7 @@ void s_cont_user(int s, int uid) { return; user_max_slots[user_id] = abs(user_max_slots[user_id]); + user_locked[user_id] = 0; struct Job *p = firstjob.next; while (p != NULL) { @@ -1063,6 +1064,7 @@ void s_stop_user(int s, int uid) { return; user_max_slots[user_id] = -abs(user_max_slots[user_id]); + user_locked[user_id] = 1; struct Job *p = firstjob.next; while (p != NULL) { diff --git a/main.h b/main.h index b074fc6..6ad1ed6 100644 --- a/main.h +++ b/main.h @@ -4,7 +4,6 @@ Please find the license in the provided COPYING file. */ -#define GET_PID "bash /home/kylin/task-spooler/kill_ppid.sh" enum { CMD_LEN = 500, PROTOCOL_VERSION = 730 }; @@ -495,6 +494,7 @@ int tail_file(const char *fname, int last_lines); /* user.c */ static const int root_UID = 0; +char *get_kill_sh_path(); void read_user_file(const char *path); int get_user_id(int uid); void c_refresh_user(); diff --git a/server_start.c b/server_start.c index a6d747d..dbcee50 100644 --- a/server_start.c +++ b/server_start.c @@ -25,6 +25,93 @@ static int should_check_owner = 0; static int fork_server(); +char *get_kill_sh_path() { + char *tmpdir; + tmpdir = getenv("TMPDIR"); + if (tmpdir == NULL) + tmpdir = "/tmp"; + char *path = NULL; + int size = strlen(tmpdir) + strlen("/kill_ppid.sh") + 1; + path = (char *)malloc(size); + snprintf(path, size, "%s/kill_ppid.sh", tmpdir); + return path; +} + +static void setup_kill_sh() { + char *path = get_kill_sh_path(); + FILE *f = fopen(path, "w"); + if (f == NULL) { + printf("Cannot create `kill_ppide.sh` file at %s\n", path); + exit(0); + } + fprintf(f, R"(#!/bin/bash + +# getting children generally resolves nicely at some point +get_child() { + echo $(pgrep -laP $1 | awk '{print $1}') +} + +get_children() { + __RET=$(get_child $1) + __CHILDREN= + while [ -n "$__RET" ]; do + __CHILDREN+="$__RET " + __RET=$(get_child $__RET) + done + + __CHILDREN=$(echo "${__CHILDREN}" | xargs | sort) + + echo "${__CHILDREN}" +} + +if [ 1 -gt $# ]; +then + echo "not input PID" + exit 1 +fi + +owner=`ps -o user= -p $1` +if [ -z "$owner" ]; +then + # echo "not a valid PID" + exit 1 +fi +pids=`get_children $1` + +user=`whoami` + +extra="" +# echo $owner $user +if [[ "$owner" != "$user" ]]; then + extra="sudo" +fi + +for pid in ${pids}; +do + if [ -n $2 ] + then + # echo ${extra} ${pid} $2 + ${extra} kill -s $2 ${pid} + fi +done + +if [ -n $2 ] +then + # echo PPID= $1 ${extra} $2 + ${extra} kill -s $2 $1 +else + echo ${extra} PPID= $1 +fi + + + +)"); + fclose(f); + + printf(" Kill_PPID.sh at `%s`\n\n", path); + free(path); +} + void create_socket_path(char **path) { char *tmpdir; char userid[20] = "root"; @@ -101,7 +188,7 @@ static void server_info() { printf(" Socket path: %s [TS_SOCKET]\n", socket_path); printf(" Read user file from %s [TS_USER_PATH]\n", get_user_path()); printf(" Write log file to %s [TS_LOGFILE_PATH]\n", set_server_logfile()); - printf(" Extra Bash CMD from `%s`\n\n", GET_PID); + setup_kill_sh(); } static void server_daemon() { diff --git a/user.c b/user.c index 2d957a2..6ebe716 100644 --- a/user.c +++ b/user.c @@ -16,6 +16,8 @@ void send_list_line(int s, const char *str); void error(const char *str, ...); +int user_locked[USER_MAX] = {0}; + const char *get_user_path() { char *str; str = getenv("TS_USER_PATH"); @@ -195,7 +197,7 @@ void s_user_status_all(int s) { char *extra; send_list_line(s, "-- Users ----------- \n"); for (size_t i = 0; i < user_number; i++) { - extra = user_max_slots[i] < 0 ? "Locked" : ""; + extra = user_locked[i] != 0 ? "Locked" : ""; snprintf(buffer, 256, "[%04d] %3d/%-4d %20s Run. %2d %s\n", user_UID[i], user_busy[i], abs(user_max_slots[i]), user_name[i], user_jobs[i], extra); @@ -208,7 +210,7 @@ void s_user_status_all(int s) { void s_user_status(int s, int i) { char buffer[256]; char *extra = ""; - if (user_max_slots[i] < 0) + if (user_locked[i] != 0) extra = "Locked"; snprintf(buffer, 256, "[%04d] %3d/%-4d %20s Run. %2d %s\n", user_UID[i], user_busy[i], abs(user_max_slots[i]), user_name[i], user_jobs[i], @@ -228,9 +230,10 @@ int get_user_id(int uid) { void kill_pid(int ppid, const char *signal) { FILE *fp; char command[1024]; - sprintf(command, GET_PID " %d %s", ppid, signal); - // printf("command = %s\n", command); + char *path = get_kill_sh_path(); + sprintf(command, "bash %s %d %s", path, ppid, signal); + printf("command = %s\n", command); fp = popen(command, "r"); - + free(path); pclose(fp); } diff --git a/user.h b/user.h index ecfce36..63bf22e 100644 --- a/user.h +++ b/user.h @@ -8,5 +8,6 @@ int user_UID[USER_MAX]; int user_busy[USER_MAX]; int user_jobs[USER_MAX]; int user_queue[USER_MAX]; +int user_locked[USER_MAX]; int user_number; char *logfile_path; \ No newline at end of file From bc4fe1e22df47e0340a99697737049afd8333237 Mon Sep 17 00:00:00 2001 From: kylin Date: Fri, 24 Feb 2023 21:46:56 +0800 Subject: [PATCH 36/91] new version with restore runing tasks --- client.c | 6 ++++++ execute.c | 16 +++++++++++--- jobs.c | 49 +++++++++++++++++++++++++++++++++++++++++++ kill_ppid.sh | 19 ++++------------- main.c | 27 ++++++++++++++++++++---- main.h | 5 +++++ restore.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++ server.c | 3 +++ server_start.c | 18 ++++------------ user.c | 24 ++++++++++++++++++++- 10 files changed, 187 insertions(+), 37 deletions(-) create mode 100755 restore.py diff --git a/client.c b/client.c index 44cce4c..6efc520 100644 --- a/client.c +++ b/client.c @@ -55,6 +55,7 @@ char *build_command_string() { } void c_new_job() { + struct Msg m = default_msg(); char *new_command; char *myenv; @@ -82,7 +83,11 @@ void c_new_job() { m.u.newjob.command_size = strlen(new_command) + 1; /* add null */ m.u.newjob.wait_enqueuing = command_line.wait_enqueuing; m.u.newjob.num_slots = command_line.num_slots; + m.u.newjob.taskpid = command_line.taskpid; + + + /* Send the message */ send_msg(server_socket, &m); @@ -590,6 +595,7 @@ void c_kill_all_jobs() { } void c_remove_job() { + printf("c_remove_job()\n"); struct Msg m = default_msg(); int res; char *string = 0; diff --git a/execute.c b/execute.c index 1570b3c..db4bdaa 100644 --- a/execute.c +++ b/execute.c @@ -54,7 +54,7 @@ static void run_parent(int fd_read_filename, int pid, struct Result *result) { /* All went fine - prepare the SIGINT and send runjob_ok */ signals_child_pid = pid; unblock_sigint_and_install_handler(); - + printf("runjob_ok %s\n", ofname); c_send_runjob_ok(ofname, pid); wait(&status); @@ -260,8 +260,18 @@ int run_job(int jobid, struct Result *res) { /* Prepare the output filename sending */ pipe(p); - - pid = fork(); + + if (command_line.taskpid == 0) { + pid = fork(); + } else { + pid = command_line.taskpid; + /* + printf("test\n"); + int namesize = strlen(out) + 1; + write(p[1], (char *)&namesize, sizeof(namesize)); + write(p[1], out, namesize); + */ + } switch (pid) { case 0: diff --git a/jobs.c b/jobs.c index e55c7b2..2deef02 100644 --- a/jobs.c +++ b/jobs.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -497,6 +498,7 @@ static int find_last_stored_jobid_finished() { /* Returns job id or -1 on error */ int s_newjob(int s, struct Msg *m) { + struct Job *p; int res; @@ -653,9 +655,56 @@ int s_newjob(int s, struct Msg *m) { ptr); free(ptr); } + + if (m->u.newjob.taskpid != 0) { + p->pid = m->u.newjob.taskpid; + struct Procinfo* pinfo = &(p->info); + pinfo->start_time.tv_sec = 0; + int num_slots = p->num_slots, id = p->user_id; + busy_slots += num_slots; + user_busy[id] += num_slots; + user_jobs[id]++; + p->state = RUNNING; + // p->state = HOLDING_CLIENT; + /* + if (p->label != NULL) + p->label[0] = '#'; + else + p->label = "new label"; + */ + + // char out[256] = "zero"; + //char* cmd = (char*) malloc(256); + char cmd[256], out[256] = "(unkown)"; + snprintf(cmd, 256, "readlink -f /proc/%d/fd/1", p->pid); + linux_cmd(cmd, out, 256); + char* f = (char*) malloc(strnlen(out, 255)+1); + strncpy(f, out, strlen(out)+1); + + struct stat t_stat; + if (stat(f, &t_stat) != -1) { + pinfo->start_time.tv_sec = t_stat.st_ctime; + } + // struct tm * timeinfo = localtime(&t_stat.st_ctime); + + p->output_filename = f; + } + return p->jobid; } + +int check_pid(int pid) { + if (pid == 0) return 0; + struct Job *p = &firstjob; + + while (p->next != NULL) { + p = p->next; + if (p->pid == pid) return 1; + } + return 0; +} + /* This assumes the jobid exists */ void s_removejob(int jobid) { struct Job *p; diff --git a/kill_ppid.sh b/kill_ppid.sh index 54d5c79..07accdf 100755 --- a/kill_ppid.sh +++ b/kill_ppid.sh @@ -15,7 +15,7 @@ get_children() { __CHILDREN=$(echo "${__CHILDREN}" | xargs | sort) - echo "${__CHILDREN}" + echo "${__CHILDREN} $1" } if [ 1 -gt $# ]; @@ -35,27 +35,16 @@ pids=`get_children $1` user=`whoami` extra="" -# echo $owner $user if [[ "$owner" != "$user" ]]; then extra="sudo" fi for pid in ${pids}; do - if [ -n $2 ] + if [ -z $2 ] then - # echo ${extra} ${pid} $2 + echo "${extra} ${pid}" + else ${extra} kill -s $2 ${pid} fi done - -if [ -n $2 ] -then - # echo PPID= $1 ${extra} $2 - ${extra} kill -s $2 $1 -else - echo ${extra} PPID= $1 -fi - - - diff --git a/main.c b/main.c index 9ec0bba..882065c 100644 --- a/main.c +++ b/main.c @@ -69,6 +69,7 @@ static void default_command_line() { command_line.num_slots = 1; command_line.require_elevel = 0; command_line.logfile = NULL; + command_line.taskpid = 0; } struct Msg default_msg() { @@ -151,7 +152,7 @@ void parse_opts(int argc, char **argv) { /* Parse options */ while (1) { c = getopt_long(argc, argv, - ":AXRTVhKzClnfmBEr:a:F:t:c:o:p:w:k:u:s:U:qi:N:L:dS:D:W:O:", + ":AXRTVhKzClnfmBEZ:a:F:t:c:o:p:w:k:r:u:s:U:qi:N:L:dS:D:W:O:", longOptions, &optionIdx); if (c == -1) @@ -213,9 +214,15 @@ void parse_opts(int argc, char **argv) { command_line.request = c_REFRESH_USER; break; case 'k': + printf("c_KILL_JOB = %s\n", optarg); command_line.request = c_KILL_JOB; command_line.jobid = str2int(optarg); break; + case 'r': + printf("c_REMOVEJOB = %s\n", optarg); + command_line.request = c_REMOVEJOB; + command_line.jobid = str2int(optarg); + break; case 'l': command_line.request = c_LIST; break; @@ -290,9 +297,21 @@ void parse_opts(int argc, char **argv) { if (command_line.num_slots < 0) command_line.num_slots = 0; break; - case 'r': - command_line.request = c_REMOVEJOB; - command_line.jobid = str2int(optarg); + case 'Z': + command_line.taskpid = str2int(optarg); + if (command_line.taskpid <= 0) + command_line.taskpid = 0; + else { + char cmd[256], out[256] = ""; + snprintf(cmd, sizeof(cmd), "readlink -f /proc/%d/fd/1", command_line.taskpid); + linux_cmd(cmd, out, sizeof(out)); + + printf("outfile: %s\n", out); + if (strlen(out) == 0) { + printf("PID: %d is dead\n", command_line.taskpid); + return; + } + } break; case 'w': command_line.request = c_WAITJOB; diff --git a/main.h b/main.h index 6ad1ed6..d125a03 100644 --- a/main.h +++ b/main.h @@ -122,6 +122,7 @@ struct CommandLine { } command; char *label; int num_slots; /* Slots for the job to use. Default 1 */ + int taskpid; /* to restore task by pid */ int require_elevel; /* whether requires error level of dependencies or not */ char *logfile; }; @@ -152,6 +153,7 @@ struct Msg { int depend_on_size; int wait_enqueuing; int num_slots; + int taskpid; } newjob; struct { int ofilename_size; @@ -507,6 +509,7 @@ void debug_write(const char *str); const char *uid2user_name(int uid); int read_first_jobid_from_logfile(const char *path); void kill_pid(int ppid, const char *signal); +char* linux_cmd(char* CMD, char* out, int out_size); /* locker */ int user_locker; @@ -529,6 +532,8 @@ void s_unlock_server(int s, int uid); int s_check_locker(int s, int uid); void s_set_jobids(int i); void s_sort_jobs(); +int check_pid(int pid); + /* client.c */ void c_list_jobs_all(); diff --git a/restore.py b/restore.py new file mode 100755 index 0000000..9b0d0d2 --- /dev/null +++ b/restore.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +import sys +import datetime +import psutil +import os + +logfile = "/home/kylin/task-spooler/log.txt" +days_num = 10 +if len(sys.argv) != 1: + days_num = int(sys.argv[2]); + exit(1) + + +def parse(s): + p1 = s.find("]") + p2 = s.find("P:") + p4 = s.find("> Pid:") + p3 = s[p2:p4].find("<")+p2 + p5 = s[p4:].find("CMD:") + p4 + p6 = s.rfind("@") + CMD = s[p5+4:p6].strip() + time_str = s[p6+1:].strip() + pid = int(s[p4+6:p5].strip()) + procs = int(s[p2+2:p3].strip()) + user = s[p1+1:p2].strip() + tag = s[p3+1:p4] + t_time = datetime.datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S") + return user, procs, pid, tag, CMD, t_time + + +print("read from", logfile) + +with open(logfile, "r") as r: + lines = [i.strip() for i in r.readlines()] + +t_now = datetime.datetime.now() +# t_line = time.gmtime() +t_line = t_now - datetime.timedelta(days = days_num) +print(f" only restore tasks with {days_num} days, start by", t_line) +tasks = [] +for l in lines[:]: + user, procs, pid, tag, CMD, t_time = parse(l) + if (psutil.pid_exists(pid)): + if (t_time > t_line): + print("add:", l) + tasks.append([tag, pid, procs, CMD]) + else: + print(" UNK:", l) + +for i in tasks[:]: + if i[0] == "..": + CMD = 'ts -Z {} -N {} "{}"'.format(*i[1:]) + else: + CMD = 'ts -L {} -Z {} -N {} "{}"'.format(*i) + print(CMD) + os.system(CMD) + diff --git a/server.c b/server.c index 9a99519..4262e35 100644 --- a/server.c +++ b/server.c @@ -442,6 +442,9 @@ static enum Break client_read(int index) { return BREAK; /* break in the parent*/ break; case NEWJOB: + if (check_pid(m.u.newjob.taskpid) == 1) { + break; + } if (user_id == -1) { break; } diff --git a/server_start.c b/server_start.c index dbcee50..2f80fce 100644 --- a/server_start.c +++ b/server_start.c @@ -61,7 +61,7 @@ get_children() { __CHILDREN=$(echo "${__CHILDREN}" | xargs | sort) - echo "${__CHILDREN}" + echo "${__CHILDREN} $1" } if [ 1 -gt $# ]; @@ -81,30 +81,20 @@ pids=`get_children $1` user=`whoami` extra="" -# echo $owner $user if [[ "$owner" != "$user" ]]; then extra="sudo" fi for pid in ${pids}; do - if [ -n $2 ] + if [ -z $2 ] then - # echo ${extra} ${pid} $2 + echo "${extra} ${pid}" + else ${extra} kill -s $2 ${pid} fi done -if [ -n $2 ] -then - # echo PPID= $1 ${extra} $2 - ${extra} kill -s $2 $1 -else - echo ${extra} PPID= $1 -fi - - - )"); fclose(f); diff --git a/user.c b/user.c index 6ebe716..346c98f 100644 --- a/user.c +++ b/user.c @@ -41,6 +41,28 @@ int get_env(const char *env, int v0) { } } +char* linux_cmd(char* CMD, char* out, int out_size) { + FILE *fp; + + /* Open the command for reading. */ + fp = popen(CMD, "r"); + if (fp == NULL) { + printf("Failed to run command: %s\n", CMD); + exit(1); + } + + /* Read the output a line at a time - output it. */ + while (fgets(out, out_size, fp) != NULL) { + ; // printf("%s", path); + } + char* end = memchr(out, '\n', out_size); + if (end != NULL) *end = '\0'; + /* close */ + pclose(fp); + return out; +} + + long str2int(const char *str) { long i; if (sscanf(str, "%ld", &i) == 0) { @@ -232,7 +254,7 @@ void kill_pid(int ppid, const char *signal) { char command[1024]; char *path = get_kill_sh_path(); sprintf(command, "bash %s %d %s", path, ppid, signal); - printf("command = %s\n", command); + // printf("command = %s\n", command); fp = popen(command, "r"); free(path); pclose(fp); From 51f5fae9f25618874eed6c1b6dcaf510e755a439 Mon Sep 17 00:00:00 2001 From: kylin Date: Sat, 25 Feb 2023 00:47:01 +0800 Subject: [PATCH 37/91] fixed the bug on the running --- client.c | 2 ++ jobs.c | 12 +++++++++++- list.c | 9 ++++++--- main.h | 3 +++ server.c | 35 +++++++++++++++++++++++++++++++++-- 5 files changed, 55 insertions(+), 6 deletions(-) diff --git a/client.c b/client.c index 6efc520..8d94c65 100644 --- a/client.c +++ b/client.c @@ -172,6 +172,8 @@ static int wait_server_lines_and_check(const char *pre_str) { if (res == 0) break; if (res != sizeof(m)) + // printf("Error in wait_server_lines 2"); + // break; error("Error in wait_server_lines 2"); if (m.type == LIST_LINE) { char *buffer; diff --git a/jobs.c b/jobs.c index 2deef02..62459f5 100644 --- a/jobs.c +++ b/jobs.c @@ -128,7 +128,7 @@ static struct Job *find_previous_job(const struct Job *final) { return NULL; } -static struct Job *findjob(int jobid) { +struct Job *findjob(int jobid) { struct Job *p; /* Show Queued or Running jobs */ @@ -366,11 +366,19 @@ void s_list(int s, int user_id) { free(buffer); /* Show Queued or Running jobs */ + char buf[256]; p = firstjob.next; while (p != 0) { + // sprintf(buf, "jobid = %d\n", p->jobid); + // send_list_line(s, buf); + if (p->state != HOLDING_CLIENT) { if (p->user_id == user_id) { buffer = joblist_line(p); + // sprintf(buf, "== jobid = %d\n", p->jobid); + // send_list_line(s, buf); + // fprintf(dbf, "----- %s\n", buffer); + // fflush(dbf); send_list_line(s, buffer); free(buffer); } @@ -378,6 +386,7 @@ void s_list(int s, int user_id) { p = p->next; } + p = first_finished_job.next; if (p != NULL && firstjob.next != NULL) send_list_line(s, "----- Finished -----\n"); @@ -390,6 +399,7 @@ void s_list(int s, int user_id) { } p = p->next; } + } void s_list_all(int s) { diff --git a/list.c b/list.c index 2be20da..ea63aa2 100644 --- a/list.c +++ b/list.c @@ -18,7 +18,7 @@ extern int busy_slots; extern int max_slots; -static int check_ifsleep(int pid) { +int check_ifsleep(int pid) { char filename[256]; char name[256]; char status = '\0'; @@ -27,7 +27,7 @@ static int check_ifsleep(int pid) { fp = fopen(filename, "r"); if (fp == NULL) { - fprintf(stderr, "Error: Couldn't open [%s]\n", filename); + // fprintf(stderr, "Error: Couldn't open [%s]\n", filename); return -1; } int token = fscanf(fp, "%d %s %c", &pid, name, &status); @@ -114,6 +114,8 @@ static const char *ofilename_shown(const struct Job *p) { } static char *print_noresult(const struct Job *p) { + // fprintf(dbf, "start print_noresult Jobid = %d\n", p->jobid); + // fflush(dbf); const char *jobstate; const char *output_filename; int maxlen; @@ -195,7 +197,8 @@ static char *print_noresult(const struct Job *p) { output_filename); free(cmd); } - + // fprintf(dbf, line); + // fflush(dbf); return line; } diff --git a/main.h b/main.h index d125a03..beec374 100644 --- a/main.h +++ b/main.h @@ -515,6 +515,9 @@ char* linux_cmd(char* CMD, char* out, int out_size); int user_locker; time_t locker_time; int jobsort_flag; +FILE* dbf; +int check_ifsleep(int pid); +struct Job *findjob(int jobid); /* jobs.c */ void s_user_status_all(int s); diff --git a/server.c b/server.c index 4262e35..c05d226 100644 --- a/server.c +++ b/server.c @@ -160,7 +160,12 @@ static int get_max_descriptors() { return max; } + void server_main(int notify_fd, char *_path) { + dbf = fopen("/home/kylin/task-spooler/debug.txt", "w"); + fprintf(dbf, "start server_main\n"); + fflush(dbf); + int ls; struct sockaddr_un addr; int res; @@ -271,14 +276,32 @@ static void server_loop(int ls) { if (FD_ISSET(client_cs[i].socket, &readset)) { enum Break b; b = client_read(i); + fprintf(dbf, "client_cs[%d].so jobid: %d\n", i, client_cs[i].jobid); + fflush(dbf); /* Check if we should break */ if (b == CLOSE) { warning("Closing"); /* On unknown message, we close the client, or it may hang waiting for an answer */ + fprintf(dbf, "CLOSE client_cs[%d].so jobid: %d\n", i, client_cs[i].jobid); + fflush(dbf); clean_after_client_disappeared(client_cs[i].socket, i); - } else if (b == BREAK) + } else if (b == BREAK) { + fprintf(dbf, "BREAK client_cs[%d].so jobid: %d\n", i, client_cs[i].jobid); + fflush(dbf); keep_loop = 0; + } + } else { + if (client_cs[i].hasjob) { + struct Job *p; + p = findjob(client_cs[i].jobid); + if (check_ifsleep(p->pid) == -1) { + clean_after_client_disappeared(client_cs[i].socket, i); + } + } + /* + + */ } /* This will return firstjob->jobid or -1 */ newjob = next_run_job(); @@ -325,6 +348,8 @@ static void remove_connection(int index) { static void clean_after_client_disappeared(int socket, int index) { /* Act as if the job ended. */ int jobid = client_cs[index].jobid; + fprintf(dbf, "clean %d from client_cs[%d]\n", jobid, index); + fflush(dbf); if (client_cs[index].hasjob) { struct Result r = default_result(); @@ -363,10 +388,14 @@ static enum Break client_read(int index) { /* Read the message */ res = recv_msg(s, &m); if (res == -1) { - warning("client recv failed"); + warning("client recv failed"); + fprintf(dbf, "start warning\n"); + fflush(dbf); clean_after_client_disappeared(s, index); return NOBREAK; } else if (res == 0) { + fprintf(dbf, "start warning clean_apeared\n"); + fflush(dbf); clean_after_client_disappeared(s, index); return NOBREAK; } @@ -457,6 +486,8 @@ static enum Break client_read(int index) { s_newjob_ok(index); else if (!m.u.newjob.wait_enqueuing) { s_newjob_nok(index); + fprintf(dbf, "start s_newjob_nok\n"); + fflush(dbf); clean_after_client_disappeared(s, index); } break; From c35c3fd79aa288f3dc359b55c90f48bd57780e60 Mon Sep 17 00:00:00 2001 From: kylin Date: Sat, 25 Feb 2023 01:14:15 +0800 Subject: [PATCH 38/91] add the function to restore tasks --- execute.c | 13 +++++++++++++ jobs.c | 2 +- list.c | 4 ++++ main.h | 2 +- server.c | 22 +++++----------------- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/execute.c b/execute.c index db4bdaa..da70e1a 100644 --- a/execute.c +++ b/execute.c @@ -14,6 +14,8 @@ #include #include #include +#include + #include #include @@ -265,6 +267,16 @@ int run_job(int jobid, struct Result *res) { pid = fork(); } else { pid = command_line.taskpid; + command_line.store_output = 0; + char cmd[256], out[256] = "(unkown)"; + snprintf(cmd, 256, "readlink -f /proc/%d/fd/1", pid); + linux_cmd(cmd, out, 256); + + struct stat t_stat; + if (stat(out, &t_stat) != -1) { + write(p[0], &t_stat.st_ctime, sizeof(t_stat.st_ctime)); + } + /* printf("test\n"); int namesize = strlen(out) + 1; @@ -292,6 +304,7 @@ int run_job(int jobid, struct Result *res) { errorlevel = 0; error("forking"); default: + printf("run_parenet\n"); close(p[1]); run_parent(p[0], pid, res); break; diff --git a/jobs.c b/jobs.c index 62459f5..131fa8f 100644 --- a/jobs.c +++ b/jobs.c @@ -366,7 +366,7 @@ void s_list(int s, int user_id) { free(buffer); /* Show Queued or Running jobs */ - char buf[256]; + // char buf[256]; p = firstjob.next; while (p != 0) { // sprintf(buf, "jobid = %d\n", p->jobid); diff --git a/list.c b/list.c index ea63aa2..69f3b08 100644 --- a/list.c +++ b/list.c @@ -210,6 +210,10 @@ static char *print_result(const struct Job *p) { /* 20 chars should suffice for a string like "[int,int,..]&& " */ char dependstr[20] = ""; float real_ms = p->result.real_ms; + if (real_ms == 0.0) { + real_ms = p->info.end_time.tv_sec - p->info.start_time.tv_sec; + real_ms += 1e-6 * (p->info.end_time.tv_usec - p->info.start_time.tv_usec); + } char *unit = time_rep(&real_ms); int cmd_len; diff --git a/main.h b/main.h index beec374..9b36214 100644 --- a/main.h +++ b/main.h @@ -515,7 +515,7 @@ char* linux_cmd(char* CMD, char* out, int out_size); int user_locker; time_t locker_time; int jobsort_flag; -FILE* dbf; +// FILE* dbf; int check_ifsleep(int pid); struct Job *findjob(int jobid); diff --git a/server.c b/server.c index c05d226..764bd07 100644 --- a/server.c +++ b/server.c @@ -162,9 +162,9 @@ static int get_max_descriptors() { void server_main(int notify_fd, char *_path) { - dbf = fopen("/home/kylin/task-spooler/debug.txt", "w"); - fprintf(dbf, "start server_main\n"); - fflush(dbf); + // dbf = fopen("/home/kylin/task-spooler/debug.txt", "w"); + // fprintf(dbf, "start server_main\n"); + // fflush(dbf); int ls; struct sockaddr_un addr; @@ -276,19 +276,13 @@ static void server_loop(int ls) { if (FD_ISSET(client_cs[i].socket, &readset)) { enum Break b; b = client_read(i); - fprintf(dbf, "client_cs[%d].so jobid: %d\n", i, client_cs[i].jobid); - fflush(dbf); /* Check if we should break */ if (b == CLOSE) { warning("Closing"); /* On unknown message, we close the client, or it may hang waiting for an answer */ - fprintf(dbf, "CLOSE client_cs[%d].so jobid: %d\n", i, client_cs[i].jobid); - fflush(dbf); clean_after_client_disappeared(client_cs[i].socket, i); } else if (b == BREAK) { - fprintf(dbf, "BREAK client_cs[%d].so jobid: %d\n", i, client_cs[i].jobid); - fflush(dbf); keep_loop = 0; } } else { @@ -348,8 +342,8 @@ static void remove_connection(int index) { static void clean_after_client_disappeared(int socket, int index) { /* Act as if the job ended. */ int jobid = client_cs[index].jobid; - fprintf(dbf, "clean %d from client_cs[%d]\n", jobid, index); - fflush(dbf); + // fprintf(dbf, "clean %d from client_cs[%d]\n", jobid, index); + // fflush(dbf); if (client_cs[index].hasjob) { struct Result r = default_result(); @@ -389,13 +383,9 @@ static enum Break client_read(int index) { res = recv_msg(s, &m); if (res == -1) { warning("client recv failed"); - fprintf(dbf, "start warning\n"); - fflush(dbf); clean_after_client_disappeared(s, index); return NOBREAK; } else if (res == 0) { - fprintf(dbf, "start warning clean_apeared\n"); - fflush(dbf); clean_after_client_disappeared(s, index); return NOBREAK; } @@ -486,8 +476,6 @@ static enum Break client_read(int index) { s_newjob_ok(index); else if (!m.u.newjob.wait_enqueuing) { s_newjob_nok(index); - fprintf(dbf, "start s_newjob_nok\n"); - fflush(dbf); clean_after_client_disappeared(s, index); } break; From ca8259fd0a238483f350d74b14ecfe09f12c911b Mon Sep 17 00:00:00 2001 From: kylin Date: Sat, 25 Feb 2023 13:27:24 +0800 Subject: [PATCH 39/91] fixed the bug --- client.c | 1 + execute.c | 5 +++-- jobs.c | 25 ++++++++++++++++++------- main.c | 25 ++++++++++++++++++++++++- main.h | 8 +++++--- restore.py | 6 +++--- server.c | 8 ++------ user.c | 2 +- user.txt | 2 +- 9 files changed, 58 insertions(+), 24 deletions(-) diff --git a/client.c b/client.c index 8d94c65..ae79161 100644 --- a/client.c +++ b/client.c @@ -84,6 +84,7 @@ void c_new_job() { m.u.newjob.wait_enqueuing = command_line.wait_enqueuing; m.u.newjob.num_slots = command_line.num_slots; m.u.newjob.taskpid = command_line.taskpid; + m.u.newjob.start_time = command_line.start_time; diff --git a/execute.c b/execute.c index da70e1a..d6deb05 100644 --- a/execute.c +++ b/execute.c @@ -267,16 +267,17 @@ int run_job(int jobid, struct Result *res) { pid = fork(); } else { pid = command_line.taskpid; + /* command_line.store_output = 0; char cmd[256], out[256] = "(unkown)"; snprintf(cmd, 256, "readlink -f /proc/%d/fd/1", pid); - linux_cmd(cmd, out, 256); + _inux_cmd(cmd, out, 256); struct stat t_stat; if (stat(out, &t_stat) != -1) { write(p[0], &t_stat.st_ctime, sizeof(t_stat.st_ctime)); } - + */ /* printf("test\n"); int namesize = strlen(out) + 1; diff --git a/jobs.c b/jobs.c index 131fa8f..dbbe1af 100644 --- a/jobs.c +++ b/jobs.c @@ -128,7 +128,8 @@ static struct Job *find_previous_job(const struct Job *final) { return NULL; } -struct Job *findjob(int jobid) { + +static struct Job *findjob(int jobid) { struct Job *p; /* Show Queued or Running jobs */ @@ -142,6 +143,16 @@ struct Job *findjob(int jobid) { return NULL; } +int check_running_dead(int jobid) { + struct Job* p = findjob(jobid); + if (p->pid != 0 && p->state == RUNNING) { + // a task is allocated by a pid and is running + return check_ifsleep(p->pid) == -1; + } + return 0; +} + + static struct Job *findjob_holding_client() { struct Job *p; @@ -472,7 +483,7 @@ static struct Job *newjobptr() { p->next->next = 0; p->next->output_filename = 0; p->next->command = 0; - + p->next->pid = 0; return p->next; } @@ -683,21 +694,21 @@ int s_newjob(int s, struct Msg *m) { p->label = "new label"; */ - // char out[256] = "zero"; - //char* cmd = (char*) malloc(256); char cmd[256], out[256] = "(unkown)"; snprintf(cmd, 256, "readlink -f /proc/%d/fd/1", p->pid); linux_cmd(cmd, out, 256); char* f = (char*) malloc(strnlen(out, 255)+1); strncpy(f, out, strlen(out)+1); - + p->output_filename = f; struct stat t_stat; if (stat(f, &t_stat) != -1) { pinfo->start_time.tv_sec = t_stat.st_ctime; + } else { + if (m->u.newjob.start_time != 0) { + pinfo->start_time.tv_sec = m->u.newjob.start_time; + } } // struct tm * timeinfo = localtime(&t_stat.st_ctime); - - p->output_filename = f; } return p->jobid; diff --git a/main.c b/main.c index 882065c..de16095 100644 --- a/main.c +++ b/main.c @@ -70,6 +70,7 @@ static void default_command_line() { command_line.require_elevel = 0; command_line.logfile = NULL; command_line.taskpid = 0; + command_line.start_time = 0; } struct Msg default_msg() { @@ -142,6 +143,8 @@ static struct option longOptions[] = { {"lock-ts", no_argument, NULL, 0}, {"unlock-ts", no_argument, NULL, 0}, {"daemon", no_argument, NULL, 0}, + {"pid", required_argument, NULL, 0}, + {"stime", required_argument, NULL, 0}, {NULL, 0, NULL, 0}}; void parse_opts(int argc, char **argv) { @@ -152,7 +155,7 @@ void parse_opts(int argc, char **argv) { /* Parse options */ while (1) { c = getopt_long(argc, argv, - ":AXRTVhKzClnfmBEZ:a:F:t:c:o:p:w:k:r:u:s:U:qi:N:L:dS:D:W:O:", + ":AXRTVhKzClnfmBE:a:F:t:c:o:p:w:k:r:u:s:U:qi:N:L:dS:D:W:O:", longOptions, &optionIdx); if (c == -1) @@ -200,6 +203,24 @@ void parse_opts(int argc, char **argv) { } else if (strcmp(longOptions[optionIdx].name, "plain") == 0) { command_line.request = c_LIST; command_line.plain_list = 1; + } else if (strcmp(longOptions[optionIdx].name, "pid") == 0) { + command_line.taskpid = str2int(optarg); + if (command_line.taskpid <= 0) + command_line.taskpid = 0; + else { + char cmd[256], out[256] = ""; + snprintf(cmd, sizeof(cmd), "readlink -f /proc/%d/fd/1", command_line.taskpid); + linux_cmd(cmd, out, sizeof(out)); + + if (strlen(out) == 0) { + printf("PID: %d is dead\n", command_line.taskpid); + return; + } else { + printf("tast stdout > %s\n", out); + } + } + } else if (strcmp(longOptions[optionIdx].name, "stime") == 0) { + command_line.start_time = str2int(optarg); } else error("Wrong option %s.", longOptions[optionIdx].name); break; @@ -297,6 +318,7 @@ void parse_opts(int argc, char **argv) { if (command_line.num_slots < 0) command_line.num_slots = 0; break; + /* case 'Z': command_line.taskpid = str2int(optarg); if (command_line.taskpid <= 0) @@ -313,6 +335,7 @@ void parse_opts(int argc, char **argv) { } } break; + */ case 'w': command_line.request = c_WAITJOB; command_line.jobid = str2int(optarg); diff --git a/main.h b/main.h index 9b36214..4ed9333 100644 --- a/main.h +++ b/main.h @@ -121,10 +121,11 @@ struct CommandLine { int num; } command; char *label; + char *logfile; int num_slots; /* Slots for the job to use. Default 1 */ int taskpid; /* to restore task by pid */ int require_elevel; /* whether requires error level of dependencies or not */ - char *logfile; + long start_time; }; enum ProcessType { CLIENT, SERVER }; @@ -154,6 +155,7 @@ struct Msg { int wait_enqueuing; int num_slots; int taskpid; + long start_time; } newjob; struct { int ofilename_size; @@ -515,9 +517,9 @@ char* linux_cmd(char* CMD, char* out, int out_size); int user_locker; time_t locker_time; int jobsort_flag; -// FILE* dbf; +// FILE* dbf; // # DEBUG int check_ifsleep(int pid); -struct Job *findjob(int jobid); +int check_running_dead(int jobid); /* jobs.c */ void s_user_status_all(int s); diff --git a/restore.py b/restore.py index 9b0d0d2..ffa4aca 100755 --- a/restore.py +++ b/restore.py @@ -43,15 +43,15 @@ def parse(s): if (psutil.pid_exists(pid)): if (t_time > t_line): print("add:", l) - tasks.append([tag, pid, procs, CMD]) + tasks.append([tag, pid, procs, int(t_time.timestamp()), CMD]) else: print(" UNK:", l) for i in tasks[:]: if i[0] == "..": - CMD = 'ts -Z {} -N {} "{}"'.format(*i[1:]) + CMD = 'ts --pid {} -N {} --stime {:} "{}"'.format(*i[1:]) else: - CMD = 'ts -L {} -Z {} -N {} "{}"'.format(*i) + CMD = 'ts -L {} --pid {} -N {} --stime {:} "{}"'.format(*i) print(CMD) os.system(CMD) diff --git a/server.c b/server.c index 764bd07..55e05aa 100644 --- a/server.c +++ b/server.c @@ -286,12 +286,8 @@ static void server_loop(int ls) { keep_loop = 0; } } else { - if (client_cs[i].hasjob) { - struct Job *p; - p = findjob(client_cs[i].jobid); - if (check_ifsleep(p->pid) == -1) { - clean_after_client_disappeared(client_cs[i].socket, i); - } + if (client_cs[i].hasjob && check_running_dead(client_cs[i].jobid) == 1) { + clean_after_client_disappeared(client_cs[i].socket, i); } /* diff --git a/user.c b/user.c index 346c98f..435de38 100644 --- a/user.c +++ b/user.c @@ -43,7 +43,6 @@ int get_env(const char *env, int v0) { char* linux_cmd(char* CMD, char* out, int out_size) { FILE *fp; - /* Open the command for reading. */ fp = popen(CMD, "r"); if (fp == NULL) { @@ -220,6 +219,7 @@ void s_user_status_all(int s) { send_list_line(s, "-- Users ----------- \n"); for (size_t i = 0; i < user_number; i++) { extra = user_locked[i] != 0 ? "Locked" : ""; + if (user_max_slots[i] == 0 && user_busy[i] == 0) continue; snprintf(buffer, 256, "[%04d] %3d/%-4d %20s Run. %2d %s\n", user_UID[i], user_busy[i], abs(user_max_slots[i]), user_name[i], user_jobs[i], extra); diff --git a/user.txt b/user.txt index 3c116f2..a21bbd8 100644 --- a/user.txt +++ b/user.txt @@ -5,6 +5,6 @@ TS_SLOTS = 4 # environment variable 1000 Kylin 10 3021 test1 10 1001 test0 100 -34 user2 30 +34 user2 10 qweq qweq qweq # automatically skipped From d083dd1310f85ac2e6af539813d9e59788bf34a9 Mon Sep 17 00:00:00 2001 From: kylin Date: Sun, 26 Feb 2023 19:45:57 +0800 Subject: [PATCH 40/91] fixed bug on the multiple user --- jobs.c | 29 +++++++++++++++++++++++++---- main.c | 5 ++++- main.h | 4 ++-- server.c | 24 +++++++++++------------- 4 files changed, 42 insertions(+), 20 deletions(-) diff --git a/jobs.c b/jobs.c index dbbe1af..6697db8 100644 --- a/jobs.c +++ b/jobs.c @@ -518,7 +518,7 @@ static int find_last_stored_jobid_finished() { } /* Returns job id or -1 on error */ -int s_newjob(int s, struct Msg *m) { +int s_newjob(int s, struct Msg *m, int user_id) { struct Job *p; int res; @@ -532,7 +532,7 @@ int s_newjob(int s, struct Msg *m) { p->state = HOLDING_CLIENT; // save the user_id and record the number of waiting jobs - p->user_id = get_user_id(m->uid); + p->user_id = user_id; // get_user_id(m->uid); user_queue[p->user_id]++; p->num_slots = m->u.newjob.num_slots; @@ -714,8 +714,7 @@ int s_newjob(int s, struct Msg *m) { return p->jobid; } - -int check_pid(int pid) { +static int isrunning_pid(int pid) { if (pid == 0) return 0; struct Job *p = &firstjob; @@ -726,6 +725,28 @@ int check_pid(int pid) { return 0; } +// if any error return 0; +int check_relink_pid(int uid, int pid) { + if (isrunning_pid(pid) == 1) { + return -1; + } + + char filename[256]; + struct stat t_stat; + + snprintf(filename, 256, "/proc/%d/stat", pid); + if (stat(filename, &t_stat) == -1) { + return -1; + } + if (uid == root_UID) { + return get_user_id(t_stat.st_uid); + } else if (uid == t_stat.st_uid) { + return get_user_id(t_stat.st_uid); + } else { + return -1; + } +} + /* This assumes the jobid exists */ void s_removejob(int jobid) { struct Job *p; diff --git a/main.c b/main.c index de16095..785145c 100644 --- a/main.c +++ b/main.c @@ -580,7 +580,10 @@ static void print_help(const char *cmd) { printf(" --cont [user] For normal user, continue all " "paused tasks and lock the account. \n " " For root, to unlock all users or single [user].\n"); - + printf(" --pid [PID] relink the existing tasks from a" + "unexpectde failure. [PID] is the process identification number"); + printf(" --stime [start_time] reset the start time of the relinked " + "by a Unix epoch."); printf("Actions:\n"); printf(" -A Show all users information\n"); printf( diff --git a/main.h b/main.h index 4ed9333..2fe2b8d 100644 --- a/main.h +++ b/main.h @@ -310,7 +310,7 @@ void s_list_all(int s); void s_list_plain(int s); -int s_newjob(int s, struct Msg *m); +int s_newjob(int s, struct Msg *m, int user_id); void s_removejob(int jobid); @@ -537,7 +537,7 @@ void s_unlock_server(int s, int uid); int s_check_locker(int s, int uid); void s_set_jobids(int i); void s_sort_jobs(); -int check_pid(int pid); +int check_relink_pid(int uid, int pid); /* client.c */ diff --git a/server.c b/server.c index 55e05aa..1e40daf 100644 --- a/server.c +++ b/server.c @@ -162,9 +162,9 @@ static int get_max_descriptors() { void server_main(int notify_fd, char *_path) { - // dbf = fopen("/home/kylin/task-spooler/debug.txt", "w"); - // fprintf(dbf, "start server_main\n"); - // fflush(dbf); + FILE* dbf = fopen("/home/kylin/task-spooler/debug.txt", "w"); + fprintf(dbf, "start server_main\n"); + fflush(dbf); int ls; struct sockaddr_un addr; @@ -457,16 +457,14 @@ static enum Break client_read(int index) { return BREAK; /* break in the parent*/ break; case NEWJOB: - if (check_pid(m.u.newjob.taskpid) == 1) { - break; - } - if (user_id == -1) { - break; - } - if (s_check_locker(s, m.uid)) { - break; - } - client_cs[index].jobid = s_newjob(s, &m); + if (m.u.newjob.taskpid != 0) { + user_id = check_relink_pid(m.uid, m.u.newjob.taskpid); + } + + if (user_id == -1) { break; } + if (s_check_locker(s, m.uid)) { break; } + + client_cs[index].jobid = s_newjob(s, &m, user_id); client_cs[index].hasjob = 1; if (!job_is_holding_client(client_cs[index].jobid)) s_newjob_ok(index); From a7a32a5c775ec71efeba330e6ec85780f6603b89 Mon Sep 17 00:00:00 2001 From: kylin Date: Mon, 27 Feb 2023 23:28:24 +0800 Subject: [PATCH 41/91] add the `relink()` in [execute.c] to monitor the existing tasks --- README.md | 21 ++++++-- execute.c | 109 ++++++++++++++++++++++++++++---------- jobs.c | 114 ++++++++++++++++++---------------------- main.c | 17 ++---- main.h | 6 ++- restore.py => relink.py | 19 +++++-- server.c | 15 ++++-- user.c | 25 +++++++++ 8 files changed, 206 insertions(+), 120 deletions(-) rename restore.py => relink.py (63%) diff --git a/README.md b/README.md index 2fdafc4..c1f5b83 100644 --- a/README.md +++ b/README.md @@ -151,17 +151,20 @@ Long option actions: --get_logdir get the path containing log files. --set_logdir [path] set the path containing log files. --plain list jobs in plain tab-separated texts. - --hold_job [jobid] hold on a task. - --restart_job [jobid] restart a task. - --lock Locker the server (Timeout: 30 sec.)git For Root, timeout is infinity. + --daemon Run the server as daemon by Root only. + --hold [jobid] hold on a task. + --restart [jobid] rerun a hold task. + --lock Locker the server (Timeout: 30 sec.)For Root, timeout is infinity. --unlock Unlocker the server. --stop [user] For normal user, pause all tasks and lock the account. For root, to lock all users or single [user]. --cont [user] For normal user, continue all paused tasks and lock the account. For root, to unlock all users or single [user]. -Actions: + --pid [PID] Relink the running tasks by its [PID] from an expected failure. + --stime [start_time] Set the relinked task by starting time (Unix epoch). + Actions: -A Show all users information - -X Refresh the user configuration (only available for root) + -X Refresh the user config by UID (Max. 100 users and only available for root) -K kill the task spooler server (only available for root) -C clear the list of finished jobs for current user -l show the job list (default action) @@ -210,6 +213,14 @@ using the `TS_USER_PATH` environment variable to specify the path to the user co qweq qweq qweq # automatically skipped ``` + +## Restore from a failure +To run the `relink.py` file with the user_log file, it would automatically relink all running tasks in a new task-spooler services. +``` +# relink.py setup +logfile = "/home/kylin/task-spooler/log.txt" # Path to the log file of tasks +days_num = 10 # only tasks starts within [days_num] will be relinked +``` ## Thanks **Author** diff --git a/execute.c b/execute.c index d6deb05..3d60782 100644 --- a/execute.c +++ b/execute.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -24,9 +25,77 @@ /* from signals.c */ extern int signals_child_pid; /* 0, not set. otherwise, set. */ + +static int wait_for_pid(int pid) +{ + char path[32]; + int in_fd = inotify_init(); + sprintf(path, "/proc/%i/exe", pid); + if (inotify_add_watch(in_fd, path, IN_CLOSE_NOWRITE) < 0) { + close(in_fd); + return -1; + } + sprintf(path, "/proc/%i", pid); + int dir_fd = open(path, 0); + if (dir_fd < 0) { + close(in_fd); + return -1; + } + + int res = 0; + while (1) { + struct inotify_event event; + if (read(in_fd, &event, sizeof(event)) < 0) { + res = -1; + break; + } + int f = openat(dir_fd, "fd", 0); + if (f < 0) break; + close(f); + } + + close(dir_fd); + close(in_fd); + return res; +} + +static void run_relink(int pid, struct Result *result) { + // int status = 0; + char *ofname = command_line.outfile; + char *command; + struct timeval endtv; + struct tms cpu_times; + + /* All went fine - prepare the SIGINT and send runjob_ok */ + signals_child_pid = pid; + unblock_sigint_and_install_handler(); + // printf("runjob_ok %s\n", ofname); + c_send_runjob_ok(ofname, pid); + + wait_for_pid(pid); + + command = build_command_string(); + if (command_line.send_output_by_mail) { + send_mail(command_line.jobid, result->errorlevel, ofname, command); + } + hook_on_finish(command_line.jobid, result->errorlevel, ofname, command); + free(command); + + free(ofname); + + /* Calculate times */ + gettimeofday(&endtv, NULL); + result->real_ms = endtv.tv_sec - command_line.start_time + + ((float)(endtv.tv_usec) / 1000000.); + times(&cpu_times); + /* The times are given in clock ticks. The number of clock ticks per second + * is obtained in POSIX using sysconf(). */ + result->user_ms = (float)cpu_times.tms_cutime / (float)sysconf(_SC_CLK_TCK); + result->system_ms = (float)cpu_times.tms_cstime / (float)sysconf(_SC_CLK_TCK); +} /* Returns errorlevel */ static void run_parent(int fd_read_filename, int pid, struct Result *result) { - int status; + int status = 0; char *ofname = 0; int namesize; int res; @@ -52,11 +121,11 @@ static void run_parent(int fd_read_filename, int pid, struct Result *result) { if (res != sizeof(starttv)) error("Reading the the struct timeval"); close(fd_read_filename); - + /* All went fine - prepare the SIGINT and send runjob_ok */ signals_child_pid = pid; unblock_sigint_and_install_handler(); - printf("runjob_ok %s\n", ofname); + // printf("runjob_ok %s\n", ofname); c_send_runjob_ok(ofname, pid); wait(&status); @@ -243,15 +312,17 @@ static void run_child(int fd_send_filename, const char *tmpdir, int jobid) { /* We create a new session, so we can kill process groups as: kill -- -`ts -p` */ setsid(); + // only execute the command without the relink flag execvp(command_line.command.array[0], command_line.command.array); } int run_job(int jobid, struct Result *res) { int pid; - int errorlevel; + int errorlevel = 0; int p[2]; char path[256]; getcwd(path, 256); + printf("start run_job()\n"); // const char *tmpdir = get_logdir(); // printf("tmpdir: %s\n", tmpdir); @@ -259,32 +330,15 @@ int run_job(int jobid, struct Result *res) { /*program_signal(); Still not needed*/ block_sigint(); - + if (command_line.taskpid != 0) { + run_relink(command_line.taskpid, res); + return errorlevel; + } /* Prepare the output filename sending */ pipe(p); - if (command_line.taskpid == 0) { - pid = fork(); - } else { - pid = command_line.taskpid; - /* - command_line.store_output = 0; - char cmd[256], out[256] = "(unkown)"; - snprintf(cmd, 256, "readlink -f /proc/%d/fd/1", pid); - _inux_cmd(cmd, out, 256); - - struct stat t_stat; - if (stat(out, &t_stat) != -1) { - write(p[0], &t_stat.st_ctime, sizeof(t_stat.st_ctime)); - } - */ - /* - printf("test\n"); - int namesize = strlen(out) + 1; - write(p[1], (char *)&namesize, sizeof(namesize)); - write(p[1], out, namesize); - */ - } + + pid = fork(); switch (pid) { case 0: @@ -305,7 +359,6 @@ int run_job(int jobid, struct Result *res) { errorlevel = 0; error("forking"); default: - printf("run_parenet\n"); close(p[1]); run_parent(p[0], pid, res); break; diff --git a/jobs.c b/jobs.c index 6697db8..3912a51 100644 --- a/jobs.c +++ b/jobs.c @@ -143,6 +143,41 @@ static struct Job *findjob(int jobid) { return NULL; } + +static int pid_in_Jobs(int pid) { + if (pid == 0) return 0; + struct Job *p = &firstjob; + + while (p->next != NULL) { + p = p->next; + if (p->pid == pid) return 1; + } + return 0; +} + +// if any error return 0; +int check_relink_pid(int uid, int pid) { + if (pid_in_Jobs(pid) == 1) { + return -1; + } + + char filename[256]; + struct stat t_stat; + + snprintf(filename, 256, "/proc/%d/stat", pid); + if (stat(filename, &t_stat) == -1) { + return -1; + } + if (uid == root_UID) { + return get_user_id(t_stat.st_uid); + } else if (uid == t_stat.st_uid) { + return get_user_id(t_stat.st_uid); + } else { + return -1; + } +} + +/* int check_running_dead(int jobid) { struct Job* p = findjob(jobid); if (p->pid != 0 && p->state == RUNNING) { @@ -151,7 +186,7 @@ int check_running_dead(int jobid) { } return 0; } - +*/ static struct Job *findjob_holding_client() { struct Job *p; @@ -363,6 +398,9 @@ const char *jstate2string(enum Jobstate s) { case HOLDING_CLIENT: jobstate = "skipped"; break; + case RELINK: + jobstate = "relink"; + break; } return jobstate; } @@ -677,6 +715,7 @@ int s_newjob(int s, struct Msg *m, int user_id) { free(ptr); } + /* for relink running task */ if (m->u.newjob.taskpid != 0) { p->pid = m->u.newjob.taskpid; struct Procinfo* pinfo = &(p->info); @@ -685,68 +724,12 @@ int s_newjob(int s, struct Msg *m, int user_id) { busy_slots += num_slots; user_busy[id] += num_slots; user_jobs[id]++; - p->state = RUNNING; - // p->state = HOLDING_CLIENT; - /* - if (p->label != NULL) - p->label[0] = '#'; - else - p->label = "new label"; - */ - - char cmd[256], out[256] = "(unkown)"; - snprintf(cmd, 256, "readlink -f /proc/%d/fd/1", p->pid); - linux_cmd(cmd, out, 256); - char* f = (char*) malloc(strnlen(out, 255)+1); - strncpy(f, out, strlen(out)+1); - p->output_filename = f; - struct stat t_stat; - if (stat(f, &t_stat) != -1) { - pinfo->start_time.tv_sec = t_stat.st_ctime; - } else { - if (m->u.newjob.start_time != 0) { - pinfo->start_time.tv_sec = m->u.newjob.start_time; - } - } - // struct tm * timeinfo = localtime(&t_stat.st_ctime); + p->state = RELINK; } return p->jobid; } -static int isrunning_pid(int pid) { - if (pid == 0) return 0; - struct Job *p = &firstjob; - - while (p->next != NULL) { - p = p->next; - if (p->pid == pid) return 1; - } - return 0; -} - -// if any error return 0; -int check_relink_pid(int uid, int pid) { - if (isrunning_pid(pid) == 1) { - return -1; - } - - char filename[256]; - struct stat t_stat; - - snprintf(filename, 256, "/proc/%d/stat", pid); - if (stat(filename, &t_stat) == -1) { - return -1; - } - if (uid == root_UID) { - return get_user_id(t_stat.st_uid); - } else if (uid == t_stat.st_uid) { - return get_user_id(t_stat.st_uid); - } else { - return -1; - } -} - /* This assumes the jobid exists */ void s_removejob(int jobid) { struct Job *p; @@ -781,6 +764,17 @@ void s_removejob(int jobid) { /* -1 if no one should be run. */ int next_run_job() { struct Job *p; + + /* If there are no jobs to run... */ + if (firstjob.next == 0) + return -1; + p = firstjob.next; + while (p != 0) { + if (p->state == RELINK) { + return p->jobid; + } + p = p->next; + } // start from a random sequence int uid = rand() % user_number; @@ -792,10 +786,6 @@ int next_run_job() { if (free_slots <= 0) return -1; - /* If there are no jobs to run... */ - if (firstjob.next == 0) - return -1; - /* Look for a runnable task */ for (int i = 0; i < user_number; i++) { uid = (uid + 1) % user_number; diff --git a/main.c b/main.c index 785145c..e71fa9c 100644 --- a/main.c +++ b/main.c @@ -208,16 +208,7 @@ void parse_opts(int argc, char **argv) { if (command_line.taskpid <= 0) command_line.taskpid = 0; else { - char cmd[256], out[256] = ""; - snprintf(cmd, sizeof(cmd), "readlink -f /proc/%d/fd/1", command_line.taskpid); - linux_cmd(cmd, out, sizeof(out)); - - if (strlen(out) == 0) { - printf("PID: %d is dead\n", command_line.taskpid); - return; - } else { - printf("tast stdout > %s\n", out); - } + check_running_task(command_line.taskpid); } } else if (strcmp(longOptions[optionIdx].name, "stime") == 0) { command_line.start_time = str2int(optarg); @@ -580,10 +571,8 @@ static void print_help(const char *cmd) { printf(" --cont [user] For normal user, continue all " "paused tasks and lock the account. \n " " For root, to unlock all users or single [user].\n"); - printf(" --pid [PID] relink the existing tasks from a" - "unexpectde failure. [PID] is the process identification number"); - printf(" --stime [start_time] reset the start time of the relinked " - "by a Unix epoch."); + printf(" --pid [PID] Relink the running tasks by its [PID] from an expected failure.\n"); + printf(" --stime [start_time] Set the relinked task by starting time (Unix epoch).\n"); printf("Actions:\n"); printf(" -A Show all users information\n"); printf( diff --git a/main.h b/main.h index 2fe2b8d..2c049ae 100644 --- a/main.h +++ b/main.h @@ -122,6 +122,7 @@ struct CommandLine { } command; char *label; char *logfile; + char *outfile; int num_slots; /* Slots for the job to use. Default 1 */ int taskpid; /* to restore task by pid */ int require_elevel; /* whether requires error level of dependencies or not */ @@ -138,7 +139,7 @@ extern int term_width; struct Msg; -enum Jobstate { QUEUED, RUNNING, FINISHED, SKIPPED, HOLDING_CLIENT }; +enum Jobstate { QUEUED, RUNNING, FINISHED, SKIPPED, HOLDING_CLIENT, RELINK }; struct Msg { enum MsgTypes type; @@ -512,12 +513,13 @@ const char *uid2user_name(int uid); int read_first_jobid_from_logfile(const char *path); void kill_pid(int ppid, const char *signal); char* linux_cmd(char* CMD, char* out, int out_size); +void check_running_task(int pid); /* locker */ int user_locker; time_t locker_time; int jobsort_flag; -// FILE* dbf; // # DEBUG +FILE* dbf; // # DEBUG int check_ifsleep(int pid); int check_running_dead(int jobid); diff --git a/restore.py b/relink.py similarity index 63% rename from restore.py rename to relink.py index ffa4aca..075d3ed 100755 --- a/restore.py +++ b/relink.py @@ -5,7 +5,7 @@ import os logfile = "/home/kylin/task-spooler/log.txt" -days_num = 10 +days_num = 1000 if len(sys.argv) != 1: days_num = int(sys.argv[2]); exit(1) @@ -42,16 +42,25 @@ def parse(s): user, procs, pid, tag, CMD, t_time = parse(l) if (psutil.pid_exists(pid)): if (t_time > t_line): - print("add:", l) - tasks.append([tag, pid, procs, int(t_time.timestamp()), CMD]) + + p = psutil.Process(pid) + cmd = " ".join(p.cmdline()).replace("/bin/sh /opt/intel/oneapi/mpi/2021.3.0/bin/mpirun", "mpirun") + if (cmd == CMD): + print("add:", l) + tasks.append([tag, pid, procs, CMD]) + # tasks.append([tag, pid, procs, int(t_time.timestamp()), CMD]) + else: + print(" N/A:", l) # "#", cmd, p.name(), p.cmdline()) else: print(" UNK:", l) for i in tasks[:]: if i[0] == "..": - CMD = 'ts --pid {} -N {} --stime {:} "{}"'.format(*i[1:]) + CMD = 'ts --pid {} -N {} "{}"'.format(*i[1:]) + # CMD = 'ts --pid {} -N {} --stime {:} "{}"'.format(*i[1:]) else: - CMD = 'ts -L {} --pid {} -N {} --stime {:} "{}"'.format(*i) + CMD = 'ts -L {} --pid {} -N {} "{}"'.format(*i) + # CMD = 'ts -L {} --pid {} -N {} --stime {:} "{}"'.format(*i) print(CMD) os.system(CMD) diff --git a/server.c b/server.c index 1e40daf..bdb0a39 100644 --- a/server.c +++ b/server.c @@ -162,7 +162,7 @@ static int get_max_descriptors() { void server_main(int notify_fd, char *_path) { - FILE* dbf = fopen("/home/kylin/task-spooler/debug.txt", "w"); + dbf = fopen("/home/kylin/task-spooler/debug.txt", "w"); fprintf(dbf, "start server_main\n"); fflush(dbf); @@ -286,15 +286,16 @@ static void server_loop(int ls) { keep_loop = 0; } } else { + /* if (client_cs[i].hasjob && check_running_dead(client_cs[i].jobid) == 1) { clean_after_client_disappeared(client_cs[i].socket, i); } - /* - */ } /* This will return firstjob->jobid or -1 */ newjob = next_run_job(); + fprintf(dbf, "start new job: %d\n", newjob); + fflush(dbf); if (newjob != -1) { int conn, awaken_job; conn = get_conn_of_jobid(newjob); @@ -379,9 +380,13 @@ static enum Break client_read(int index) { res = recv_msg(s, &m); if (res == -1) { warning("client recv failed"); + fprintf(dbf, "start clean_after_clieeared\n"); + fflush(dbf); clean_after_client_disappeared(s, index); return NOBREAK; } else if (res == 0) { + fprintf(dbf, "start clean_after_clie2\n"); + fflush(dbf); clean_after_client_disappeared(s, index); return NOBREAK; } @@ -458,6 +463,7 @@ static enum Break client_read(int index) { break; case NEWJOB: if (m.u.newjob.taskpid != 0) { + // check if taskpid isnot in queue and from a valid user. user_id = check_relink_pid(m.uid, m.u.newjob.taskpid); } @@ -650,7 +656,8 @@ static void s_runjob(int jobid, int index) { error("Run job of the client %i which doesn't have any job", index); s = client_cs[index].socket; - + fprintf(dbf, "socket = %d, jobid = %d\n", s, jobid); + fflush(dbf); s_send_runjob(s, jobid); } diff --git a/user.c b/user.c index 435de38..a1b1af9 100644 --- a/user.c +++ b/user.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "main.h" #include "user.h" @@ -28,6 +29,7 @@ const char *get_user_path() { } } + int get_env(const char *env, int v0) { char *str; str = getenv(env); @@ -79,6 +81,29 @@ const char *set_server_logfile() { return logfile_path; } +void check_running_task(int pid) { + char cmd[256], filename[256] = ""; + snprintf(cmd, sizeof(cmd), "readlink -f /proc/%d/fd/1", command_line.taskpid); + linux_cmd(cmd, filename, sizeof(filename)); + + int namesize = strnlen(filename, 255)+1; + char* f = (char*) malloc(namesize); + strncpy(f, filename, namesize); + if (strlen(f) == 0) { + error("PID: %d is dead", pid); + } else { + printf("tast stdout > %s\n", filename); + } + struct stat t_stat; + snprintf(filename, 256, "/proc/%d/stat", pid); + if (stat(filename, &t_stat) != -1) { + command_line.start_time = t_stat.st_ctime; + } + command_line.outfile = f; +} + + + void write_logfile(const struct Job *p) { // char buf[1024] = ""; FILE *f = fopen(logfile_path, "a"); From 6eba03551ce71b6b3c1325bf59dcc261f71d2f74 Mon Sep 17 00:00:00 2001 From: kylin Date: Thu, 2 Mar 2023 08:59:47 +0800 Subject: [PATCH 42/91] add the credential for socket connection --- client.c | 10 +-- jobs.c | 196 ++++++++++++++++++++++++------------------------- list.c | 8 +- main.c | 15 +++- main.h | 15 ++-- server.c | 119 ++++++++++++++++++------------ server_start.c | 10 ++- user.c | 31 ++++---- user.h | 9 ++- 9 files changed, 224 insertions(+), 189 deletions(-) diff --git a/client.c b/client.c index ae79161..0894dc2 100644 --- a/client.c +++ b/client.c @@ -14,6 +14,7 @@ #include #include "main.h" +extern int client_uid; static void c_end_of_job(const struct Result *res); @@ -68,7 +69,6 @@ void c_new_job() { /* global */ m.u.newjob.command_size = strlen(new_command) + 1; /* add null */ - m.uid = client_uid; if (myenv) m.u.newjob.env_size = strlen(myenv) + 1; /* add null */ else @@ -375,7 +375,7 @@ static void c_end_of_job(const struct Result *res) { void c_shutdown_server() { struct Msg m = default_msg(); - if (m.uid != 0) { + if (client_uid != 0) { printf("Only the root can shutdown the ts server\n"); return; } @@ -394,7 +394,7 @@ void c_shutdown_server() { void c_clear_finished() { struct Msg m = default_msg(); - if (m.uid == 0) { + if (client_uid == 0) { char buf[10]; printf("Do you want to clear all the finished jobs on the task-spooler " "server? (Yes/no) "); @@ -561,7 +561,7 @@ void c_kill_job() { void c_kill_all_jobs() { struct Msg m = default_msg(); - if (m.uid != 0) { + if (client_uid != 0) { printf("Only the root can shutdown the ts server\n"); return; } @@ -606,7 +606,7 @@ void c_remove_job() { /* Send the request */ m.type = REMOVEJOB; m.jobid = command_line.jobid; - m.uid = client_uid; + send_msg(server_socket, &m); /* Receive the answer */ diff --git a/jobs.c b/jobs.c index 3912a51..c58e93d 100644 --- a/jobs.c +++ b/jobs.c @@ -156,7 +156,7 @@ static int pid_in_Jobs(int pid) { } // if any error return 0; -int check_relink_pid(int uid, int pid) { +int check_relink_pid(int ts_UID, int pid) { if (pid_in_Jobs(pid) == 1) { return -1; } @@ -166,12 +166,14 @@ int check_relink_pid(int uid, int pid) { snprintf(filename, 256, "/proc/%d/stat", pid); if (stat(filename, &t_stat) == -1) { - return -1; + return -1; } - if (uid == root_UID) { - return get_user_id(t_stat.st_uid); - } else if (uid == t_stat.st_uid) { - return get_user_id(t_stat.st_uid); + + int job_tsUID = get_tsUID(t_stat.st_uid); + if (ts_UID == 0) { + return job_tsUID; + } else if (ts_UID == job_tsUID) { + return job_tsUID; } else { return -1; } @@ -279,12 +281,12 @@ void s_count_running_jobs(int s) { send_msg(s, &m); } -int s_get_job_uid(int jobid) { +int s_get_job_tsUID(int jobid) { struct Job *p = get_job(jobid); if (p == NULL) { return -1; } else { - return user_UID[p->user_id]; + return p->ts_UID; } } @@ -405,7 +407,7 @@ const char *jstate2string(enum Jobstate s) { return jobstate; } -void s_list(int s, int user_id) { +void s_list(int s, int ts_UID) { struct Job *p; char *buffer; @@ -422,7 +424,7 @@ void s_list(int s, int user_id) { // send_list_line(s, buf); if (p->state != HOLDING_CLIENT) { - if (p->user_id == user_id) { + if (p->ts_UID == ts_UID) { buffer = joblist_line(p); // sprintf(buf, "== jobid = %d\n", p->jobid); // send_list_line(s, buf); @@ -441,7 +443,7 @@ void s_list(int s, int user_id) { send_list_line(s, "----- Finished -----\n"); /* Show Finished jobs */ while (p != 0) { - if (p->user_id == user_id) { + if (p->ts_UID == ts_UID) { buffer = joblist_line(p); send_list_line(s, buffer); free(buffer); @@ -556,7 +558,7 @@ static int find_last_stored_jobid_finished() { } /* Returns job id or -1 on error */ -int s_newjob(int s, struct Msg *m, int user_id) { +int s_newjob(int s, struct Msg *m, int ts_UID) { struct Job *p; int res; @@ -569,9 +571,9 @@ int s_newjob(int s, struct Msg *m, int user_id) { else p->state = HOLDING_CLIENT; - // save the user_id and record the number of waiting jobs - p->user_id = user_id; // get_user_id(m->uid); - user_queue[p->user_id]++; + // save the ts_UID and record the number of waiting jobs + p->ts_UID = ts_UID; // get_tsUID(m->uid); + user_queue[p->ts_UID]++; p->num_slots = m->u.newjob.num_slots; p->store_output = m->u.newjob.store_output; @@ -720,7 +722,7 @@ int s_newjob(int s, struct Msg *m, int user_id) { p->pid = m->u.newjob.taskpid; struct Procinfo* pinfo = &(p->info); pinfo->start_time.tv_sec = 0; - int num_slots = p->num_slots, id = p->user_id; + int num_slots = p->num_slots, id = p->ts_UID; busy_slots += num_slots; user_busy[id] += num_slots; user_jobs[id]++; @@ -813,7 +815,7 @@ int next_run_job() { continue; } - int num_slots = p->num_slots, id = p->user_id; + int num_slots = p->num_slots, id = p->ts_UID; if (id == uid && free_slots >= num_slots && user_max_slots[id] - user_busy[id] >= num_slots) { busy_slots += num_slots; @@ -914,8 +916,8 @@ void job_finished(const struct Result *result, int jobid) { * connection. */ if (p->state == RUNNING) { busy_slots = busy_slots - p->num_slots; - user_busy[p->user_id] -= p->num_slots; - user_jobs[p->user_id]--; + user_busy[p->ts_UID] -= p->num_slots; + user_jobs[p->ts_UID]--; } /* Mark state */ @@ -959,7 +961,7 @@ void job_finished(const struct Result *result, int jobid) { } } -void s_clear_finished(int user_id) { +void s_clear_finished(int ts_UID) { struct Job *p, *other_user_job = &first_finished_job; if (first_finished_job.next == NULL) return; @@ -969,7 +971,7 @@ void s_clear_finished(int user_id) { while (p != NULL) { struct Job *tmp; tmp = p->next; - if (p->user_id == user_id || user_id == -100) { + if (p->ts_UID == ts_UID || ts_UID == 0) { destroy_job(p); } else { other_user_job->next = p; @@ -1069,8 +1071,9 @@ void s_job_info(int s, int jobid) { } write(s, p->command, strlen(p->command)); fd_nprintf(s, 100, "\n"); - fd_nprintf(s, 100, "User: %s\n", user_name[p->user_id]); - fd_nprintf(s, 100, "Slots required: %i\n", p->num_slots); + fd_nprintf(s, 100, "User: %s [%d]\n", user_name[p->ts_UID], user_UID[p->ts_UID]); + fd_nprintf(s, 100, "Slots required: %4i; PID: %d\n", + p->num_slots, p->pid); fd_nprintf(s, 100, "Output: %s\n", p->output_filename); fd_nprintf(s, 100, "Enqueue time: %s", ctime(&p->info.enqueue_time.tv_sec)); if (p->state == RUNNING) { @@ -1083,7 +1086,7 @@ void s_job_info(int s, int jobid) { fd_nprintf(s, 100, "End time: %s", ctime(&p->info.end_time.tv_sec)); float t = pinfo_time_run(&p->info); char *unit = time_rep(&t); - fd_nprintf(s, 100, "Time run: %f%s\n", t, unit); + fd_nprintf(s, 100, "Time run: %:.4f %s\n", t, unit); } fd_nprintf(s, 100, "\n"); } @@ -1102,29 +1105,28 @@ void s_refresh_users(int s) { } void s_stop_all_users(int s) { - for (int i = 0; i < user_number; i++) { - s_stop_user(s, user_UID[i]); + for (int i = 1; i < user_number; i++) { + s_stop_user(s, i); } } void s_cont_all_users(int s) { - for (int i = 0; i < user_number; i++) { - s_cont_user(s, user_UID[i]); + for (int i = 1; i < user_number; i++) { + s_cont_user(s, i); } } -void s_cont_user(int s, int uid) { - // get the sequence of user_id - int user_id = get_user_id(uid); - if (user_id == -1) +void s_cont_user(int s, int ts_UID) { + // get the sequence of ts_UID + if (ts_UID < 0 || ts_UID > USER_MAX) return; - user_max_slots[user_id] = abs(user_max_slots[user_id]); - user_locked[user_id] = 0; + user_max_slots[ts_UID] = abs(user_max_slots[ts_UID]); + user_locked[ts_UID] = 0; struct Job *p = firstjob.next; while (p != NULL) { - if (p->user_id == user_id && p->state == RUNNING) { + if (p->ts_UID == ts_UID && p->state == RUNNING) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { // kill(p->pid, SIGCONT); @@ -1134,22 +1136,21 @@ void s_cont_user(int s, int uid) { p = p->next; } - snprintf(buff, 255, "Resume user: [%d] %s\n", uid, user_name[user_id]); + snprintf(buff, 255, "Resume user: [%d] %s\n", user_UID[ts_UID], user_name[ts_UID]); send_list_line(s, buff); } -void s_stop_user(int s, int uid) { - // get the sequence of user_id - int user_id = get_user_id(uid); - if (user_id == -1) +void s_stop_user(int s, int ts_UID) { + // get the sequence of ts_UID + if (ts_UID < 0 || ts_UID > USER_MAX) return; - user_max_slots[user_id] = -abs(user_max_slots[user_id]); - user_locked[user_id] = 1; + user_max_slots[ts_UID] = -abs(user_max_slots[ts_UID]); + user_locked[ts_UID] = 1; struct Job *p = firstjob.next; while (p != NULL) { - if (p->user_id == user_id && p->state == RUNNING) { + if (p->ts_UID == ts_UID && p->state == RUNNING) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { // kill(p->pid, SIGSTOP); @@ -1159,14 +1160,14 @@ void s_stop_user(int s, int uid) { if (p->label != NULL) label = p->label; snprintf(buff, 255, "Error in stop %s [%d] %s | %s\n", - user_name[user_id], p->jobid, label, p->command); + user_name[ts_UID], p->jobid, label, p->command); send_list_line(s, buff); } } p = p->next; } - snprintf(buff, 255, "Lock user: [%d] %s\n", uid, user_name[user_id]); + snprintf(buff, 255, "Lock user: [%d] %s\n", user_UID[ts_UID], user_name[ts_UID]); send_list_line(s, buff); } @@ -1246,11 +1247,17 @@ void notify_errorlevel(struct Job *p) { /* jobid is input/output. If the input is -1, it's changed to the jobid * removed */ -int s_remove_job(int s, int *jobid, int client_uid) { +int s_remove_job(int s, int *jobid, int client_tsUID) { struct Job *p = 0; struct Msg m = default_msg(); struct Job *before_p = &firstjob; + if (client_tsUID < 0 || client_tsUID > USER_MAX) { + snprintf(buff, 255, "invalid ts_UID [%d] in job removal.\n", client_tsUID); + send_list_line(s, buff); + return 0; + } + if (*jobid == -1) { /* Find the last job added */ p = firstjob.next; @@ -1288,22 +1295,20 @@ int s_remove_job(int s, int *jobid, int client_uid) { } } - // if (p == NULL || p == firstjob || user_UID[p->user_id] != client_uid) { - if (p != NULL && client_uid == 0) { - client_uid = user_UID[p->user_id]; + if (p != NULL && client_tsUID == 0) { + client_tsUID = p->ts_UID; } - if (p == NULL || (user_UID[p->user_id] != client_uid)) { + if (p == NULL || (p->ts_UID != client_tsUID)) { if (*jobid == -1) snprintf(buff, 255, "The last job cannot be removed.\n"); else snprintf(buff, 255, "The job %i cannot be removed.\n", *jobid); if (p != NULL) { - int id = p->user_id; - if (user_UID[id] != client_uid) { - snprintf(buff, 255, "The job %i belongs to user:%s not uid:%d.\n", - *jobid, user_name[id], client_uid); + if (p->ts_UID != client_tsUID) { + snprintf(buff, 255, "The job %i belongs to %s not %s.\n", + *jobid, user_name[p->ts_UID], user_name[client_tsUID]); } } send_list_line(s, buff); @@ -1311,17 +1316,18 @@ int s_remove_job(int s, int *jobid, int client_uid) { } if (p->state == RUNNING) { - if (p->pid != 0 && (user_UID[p->user_id] == client_uid)) { + if (p->pid != 0 && (p->ts_UID == client_tsUID)) { // kill((p->pid), SIGTERM); // kill_pid(p->pid, "-9"); if (*jobid == -1) snprintf(buff, 255, "Running job of last job is removed.\n"); else - snprintf(buff, 255, "Running job %i[%d] is removed.\n", *jobid, p->pid); + snprintf(buff, 255, "Running job [%i] PID: %d by `%s` is removed.\n", *jobid, p->pid, user_name[p->ts_UID]); send_list_line(s, buff); return 0; } + send_list_line(s, "RUNNING\n"); return 0; } @@ -1400,7 +1406,7 @@ static struct Job *get_job(int jobid) { return 0; } -int s_check_locker(int s, int uid) { +int s_check_locker(int s, int ts_UID) { int dt = time(NULL) - locker_time; int res; @@ -1411,7 +1417,7 @@ int s_check_locker(int s, int uid) { if (user_locker == -1) { res = 0; } else { - if (user_locker == uid) { + if (user_locker == ts_UID) { res = 0; } else { res = 1; @@ -1421,70 +1427,58 @@ int s_check_locker(int s, int uid) { return res; } -void s_lock_server(int s, int uid) { - if (uid == 0) { +void s_lock_server(int s, int ts_UID) { + if (ts_UID == 0) { user_locker = 0; locker_time = time(NULL); snprintf(buff, 255, "lock the task-spooler server by Root\n"); } else { if (user_locker == -1) { - int user_id = get_user_id(uid); - if (user_id != -1) { - user_locker = uid; - locker_time = time(NULL); - snprintf(buff, 255, "lock the task-spooler server by `%s`\n", - user_name[user_id]); - } else { - snprintf(buff, 255, - "Error: cannot lock the task-spooler server by UID: [%d]\n", - uid); - } + user_locker = ts_UID; + locker_time = time(NULL); + snprintf(buff, 255, "lock the task-spooler server by [%d] `%s`\n", + user_UID[user_locker], user_name[ts_UID]); } else { - if (user_locker == uid) { + if (user_locker == ts_UID) { snprintf(buff, 255, - "The task-spooler server has already been locked by `%s`\n", - uid2user_name(uid)); + "The task-spooler server has already been locked by [%d] `%s`\n", + user_UID[user_locker], user_name[user_locker]); } else { snprintf(buff, 255, - "Error: the task-spooler server cannot be locked by `%s`\n", - uid2user_name(uid)); + "Error: the task-spooler server has already been locked by other user [%d] `%s`\n", + user_UID[user_locker], user_name[user_locker]); } } } send_list_line(s, buff); } -void s_unlock_server(int s, int uid) { - if (uid == 0) { +void s_unlock_server(int s, int ts_UID) { + if (user_locker == -1) { + snprintf(buff, 255, + "The task-spooler server has already been unlocked\n"); + } else { + if (ts_UID == 0) { user_locker = -1; snprintf(buff, 255, "Unlock the task-spooler server by Root\n"); - } else { - if (user_locker == uid) { - int user_id = get_user_id(uid); - if (user_id != -1) { - user_locker = -1; - snprintf(buff, 255, "Unlock the task-spooler server by `%s`\n", - user_name[user_id]); - } else { - snprintf(buff, 255, - "Error: cannot lock the task-spooler server by UID: [%d]\n", - uid); - } } else { - if (user_locker == -1) { - snprintf(buff, 255, - "The task-spooler server has already been unlocked\n"); + if (user_locker == ts_UID) { + user_locker = -1; + snprintf(buff, 255, "Unlock the task-spooler server by [%d] `%s`\n", + user_UID[ts_UID], user_name[ts_UID]); } else { snprintf(buff, 255, - "Error: the task-spooler server cannot be unlocked by `%s`\n", - uid2user_name(uid)); + "Error: the task-spooler server locked by other user cannot be unlocked by [%d] `%s`\n", + user_UID[ts_UID], user_name[ts_UID]); + } } + } send_list_line(s, buff); } -void s_hold_job(int s, int jobid, int uid) { +void s_hold_job(int s, int jobid, int ts_UID) { struct Job *p; p = findjob(jobid); if (p == 0) { @@ -1493,8 +1487,8 @@ void s_hold_job(int s, int jobid, int uid) { return; } - int job_UID = user_UID[p->user_id]; - if (p->pid != 0 && (job_UID = uid || uid == 0)) { + int job_tsUID = p->ts_UID; + if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { // kill(p->pid, SIGSTOP); // kill_pid(p->pid, "-stop"); snprintf(buff, 255, "Hold on job [%d] successfully!\n", jobid); @@ -1505,7 +1499,7 @@ void s_hold_job(int s, int jobid, int uid) { send_list_line(s, buff); } -void s_restart_job(int s, int jobid, int uid) { +void s_restart_job(int s, int jobid, int ts_UID) { struct Job *p; p = findjob(jobid); @@ -1516,8 +1510,8 @@ void s_restart_job(int s, int jobid, int uid) { return; } - int job_UID = user_UID[p->user_id]; - if (p->pid != 0 && (job_UID = uid || uid == 0)) { + int job_tsUID = p->ts_UID; + if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { // kill(p->pid, SIGCONT); // kill_pid(p->pid, "-cont"); snprintf(buff, 255, "Restart job [%d] successfully!\n", jobid); diff --git a/list.c b/list.c index 69f3b08..12e995f 100644 --- a/list.c +++ b/list.c @@ -76,7 +76,7 @@ char *joblist_headers() { if (user_locker != -1) { time_t dt = time(NULL) - locker_time; snprintf(extra, 100, "Locked by `%s` for %ld sec.", - uid2user_name(user_locker), dt); + user_name[user_locker], dt); } line = malloc(256); @@ -136,7 +136,7 @@ static char *print_noresult(const struct Job *p) { } output_filename = ofilename_shown(p); - char *uname = user_name[p->user_id]; + char *uname = user_name[p->ts_UID]; maxlen = 4 + 1 + 10 + 1 + 20 + 1 + 8 + 1 + 25 + 1 + strlen(p->command) + 20 + strlen(uname) + 240 + strlen(output_filename); /* 20 is the margin for errors */ @@ -220,7 +220,7 @@ static char *print_result(const struct Job *p) { jobstate = jstate2string(p->state); output_filename = ofilename_shown(p); - char *uname = user_name[p->user_id]; + char *uname = user_name[p->ts_UID]; maxlen = 4 + 1 + 10 + 1 + 20 + 1 + 8 + 1 + 25 + 1 + strlen(p->command) + 20 + strlen(uname) + 240 + strlen(output_filename); /* 20 is the margin for errors */ @@ -281,7 +281,7 @@ static char *plainprint_noresult(const struct Job *p) { jobstate = jstate2string(p->state); output_filename = ofilename_shown(p); - char *uname = user_name[p->user_id]; + char *uname = user_name[p->ts_UID]; maxlen = 4 + 1 + 10 + 1 + 20 + 1 + 8 + 1 + 25 + 1 + strlen(p->command) + 20 + strlen(uname) + 2; /* 20 is the margin for errors */ diff --git a/main.c b/main.c index e71fa9c..a8fc770 100644 --- a/main.c +++ b/main.c @@ -22,7 +22,7 @@ #include #include "main.h" - +int client_uid; extern char *optarg; extern int optind, opterr, optopt; @@ -76,7 +76,6 @@ static void default_command_line() { struct Msg default_msg() { struct Msg m; memset(&m, 0, sizeof(struct Msg)); - m.uid = getuid(); return m; } @@ -719,7 +718,11 @@ int main(int argc, char **argv) { return -1; } } - printf("Stop user ID: %d\n", stop_uid); + if (stop_uid != 0) { + printf("To stop user ID: %d\n", stop_uid); + } else { + printf("To stop all users by `Root`\n"); + } c_stop_user(stop_uid); c_wait_server_lines(); } break; @@ -738,7 +741,11 @@ int main(int argc, char **argv) { return -1; } } - printf("Resume user ID: %d\n", cont_uid); + if (cont_uid != 0) { + printf("To resume user ID: %d\n", cont_uid); + } else { + printf("To rResume all users by `Root`\n"); + } c_cont_user(cont_uid); c_wait_server_lines(); } break; diff --git a/main.h b/main.h index 2c049ae..8f3f202 100644 --- a/main.h +++ b/main.h @@ -143,7 +143,6 @@ enum Jobstate { QUEUED, RUNNING, FINISHED, SKIPPED, HOLDING_CLIENT, RELINK }; struct Msg { enum MsgTypes type; - int uid; int jobid; union { struct { @@ -208,7 +207,7 @@ struct Job { char *output_filename; int store_output; int pid; - int user_id; + int ts_UID; int should_keep_finished; int *depend_on; int depend_on_size; @@ -225,7 +224,7 @@ enum ExitCodes { EXITCODE_UNKNOWN_ERROR = -1, EXITCODE_QUEUE_FULL = 2 }; -int client_uid; + /* main.c */ struct Msg default_msg(); @@ -306,12 +305,12 @@ void c_set_env(); void c_unset_env(); /* jobs.c */ -void s_list(int s, int user_id); +void s_list(int s, int ts_UID); void s_list_all(int s); void s_list_plain(int s); -int s_newjob(int s, struct Msg *m, int user_id); +int s_newjob(int s, struct Msg *m, int ts_UID); void s_removejob(int jobid); @@ -321,7 +320,7 @@ int next_run_job(); void s_mark_job_running(int jobid); -void s_clear_finished(int user_id); +void s_clear_finished(int ts_UID); void s_process_runjob_ok(int jobid, char *oname, int pid); @@ -501,7 +500,7 @@ int tail_file(const char *fname, int last_lines); static const int root_UID = 0; char *get_kill_sh_path(); void read_user_file(const char *path); -int get_user_id(int uid); +int get_tsUID(int uid); void c_refresh_user(); const char *get_user_path(); const char *set_server_logfile(); @@ -527,7 +526,7 @@ int check_running_dead(int jobid); void s_user_status_all(int s); void s_user_status(int s, int i); void s_refresh_users(int s); -int s_get_job_uid(int jobid); +int s_get_job_tsUID(int jobid); void s_stop_all_users(int s); void s_stop_user(int s, int uid); void s_cont_user(int s, int uid); diff --git a/server.c b/server.c index bdb0a39..f8e692b 100644 --- a/server.c +++ b/server.c @@ -23,6 +23,14 @@ #include #include #include + +#ifdef HAVE_UCRED_H +#include +#endif +#ifdef HAVE_SYS_UCRED_H +#include +#endif + #include #include "main.h" @@ -53,6 +61,7 @@ struct Client_conn { int socket; int hasjob; int jobid; + int ts_UID; }; /* Globals */ @@ -202,7 +211,11 @@ void server_main(int notify_fd, char *_path) { if (res == -1) error("Error listening."); - user_number = 0; + // setup root user + user_number = 1; + user_UID[0] = 0; + user_max_slots[0] = 0; + strcpy(user_name[0], "Root"); for (int i = 0; i < USER_MAX; i++) { user_busy[i] = 0; user_jobs[i] = 0; @@ -267,9 +280,23 @@ static void server_loop(int ls) { cs = accept(ls, NULL, NULL); if (cs == -1) error("Accepting from %i", ls); + + + struct ucred scred; + unsigned int len = sizeof(struct ucred); + if (getsockopt(cs, SOL_SOCKET, SO_PEERCRED, &scred, &len) == -1) + error("cannot read peer credentials from %i", cs); + client_cs[nconnections].hasjob = 0; client_cs[nconnections].socket = cs; - ++nconnections; + + client_cs[nconnections].ts_UID = get_tsUID(scred.uid); + + if (client_cs[nconnections].ts_UID == -1) { + close(cs); + } else { + ++nconnections; + } } for (i = 0; i < nconnections; ++i) @@ -390,87 +417,88 @@ static enum Break client_read(int index) { clean_after_client_disappeared(s, index); return NOBREAK; } - int user_id = get_user_id(m.uid); + int ts_UID = client_cs[index].ts_UID; /* Process message */ switch (m.type) { case REFRESH_USERS: - if (m.uid == getuid()) { + if (ts_UID == 0) { s_refresh_users(s); } close(s); remove_connection(index); break; case LOCK_SERVER: - s_lock_server(s, m.uid); + s_lock_server(s, ts_UID); close(s); remove_connection(index); break; case UNLOCK_SERVER: - s_unlock_server(s, m.uid); + s_unlock_server(s, ts_UID); close(s); remove_connection(index); break; case HOLD_JOB: - s_hold_job(s, m.jobid, m.uid); + s_hold_job(s, m.jobid, ts_UID); close(s); remove_connection(index); break; case RESTART_JOB: - s_restart_job(s, m.jobid, m.uid); + s_restart_job(s, m.jobid, ts_UID); close(s); remove_connection(index); break; case STOP_USER: - if (m.uid == getuid()) { + // Root, uid in m.jobid + if (ts_UID == 0) { if (m.jobid != 0) { - s_stop_user(s, m.jobid); - s_user_status_all(s); + s_stop_user(s, get_tsUID(m.jobid)); + s_user_status(s, get_tsUID(m.jobid)); } else { s_stop_all_users(s); - s_user_status(s, get_user_id(m.jobid)); + s_user_status_all(s); } } else { - if (m.uid == m.jobid) { - s_stop_user(s, m.jobid); - s_user_status(s, get_user_id(m.jobid)); - } + s_stop_user(s, ts_UID); + s_user_status(s, ts_UID); } close(s); remove_connection(index); break; case CONT_USER: - if (m.uid == getuid()) { + if (ts_UID == 0) { if (m.jobid != 0) { - s_cont_user(s, m.jobid); - s_user_status_all(s); + s_cont_user(s, get_tsUID(m.jobid)); + s_user_status(s, get_tsUID(m.jobid)); } else { s_cont_all_users(s); - s_user_status(s, get_user_id(m.jobid)); + s_user_status_all(s); } } else { - if (m.uid == m.jobid) { - s_cont_user(s, m.jobid); - s_user_status(s, get_user_id(m.jobid)); - } + s_cont_user(s, ts_UID); + s_user_status(s, ts_UID); } close(s); remove_connection(index); break; case KILL_SERVER: - if (m.uid == getuid()) + if (ts_UID == 0) return BREAK; /* break in the parent*/ break; case NEWJOB: if (m.u.newjob.taskpid != 0) { // check if taskpid isnot in queue and from a valid user. - user_id = check_relink_pid(m.uid, m.u.newjob.taskpid); - } + fprintf(dbf, "ts_UID = %d, taskpid = %d\n", ts_UID, m.u.newjob.taskpid); + ts_UID = ts(ts_UID, m.u.newjob.taskpid); + // fprintf(dbf, "ts_UID = %d, taskpid = %d\n", ts_UID, m.u.newjob.taskpid); + // fflush(dbf); + } else { + if (s_check_locker(s, ts_UID) == 1) { break; } + } - if (user_id == -1) { break; } - if (s_check_locker(s, m.uid)) { break; } + if (ts_UID < 0 || ts_UID > USER_MAX) { break; } - client_cs[index].jobid = s_newjob(s, &m, user_id); + client_cs[index].jobid = s_newjob(s, &m, ts_UID); client_cs[index].hasjob = 1; if (!job_is_holding_client(client_cs[index].jobid)) s_newjob_ok(index); @@ -491,7 +519,7 @@ static enum Break client_read(int index) { s_process_runjob_ok(client_cs[index].jobid, buffer, m.u.output.pid); } break; case KILL_ALL: - if (m.uid == 0) + if (ts_UID == 0) s_kill_all_jobs(s); break; case LIST: @@ -499,8 +527,8 @@ static enum Break client_read(int index) { if (m.u.list.plain_list) s_list_plain(s); else - s_list(s, user_id); - s_user_status(s, user_id); + s_list(s, ts_UID); + s_user_status(s, ts_UID); /* We must actively close, meaning End of Lines */ close(s); remove_connection(index); @@ -540,12 +568,7 @@ static enum Break client_read(int index) { client_cs[index].hasjob = 0; break; case CLEAR_FINISHED: - if (user_id != -1) - s_clear_finished(user_id); - if (m.uid == 0) { - // clear all finished by all users - s_clear_finished(-100); - } + s_clear_finished(ts_UID); break; case ASK_OUTPUT: s_send_output(s, m.jobid); @@ -553,7 +576,7 @@ static enum Break client_read(int index) { case REMOVEJOB: { int went_ok; /* Will update the jobid. If it's -1, will set the jobid found */ - went_ok = s_remove_job(s, &m.jobid, m.uid); + went_ok = s_remove_job(s, &m.jobid, ts_UID); if (went_ok) { int i; for (i = 0; i < nconnections; ++i) { @@ -580,7 +603,7 @@ static enum Break client_read(int index) { s_count_running_jobs(s); break; case URGENT: - if (m.uid == 0 || m.uid == s_get_job_uid(m.jobid)) { + if (ts_UID == 0 || ts_UID == s_get_job_tsUID(m.jobid)) { s_move_urgent(s, m.jobid); } if (jobsort_flag) @@ -589,7 +612,7 @@ static enum Break client_read(int index) { remove_connection(index); break; case SET_MAX_SLOTS: - if (m.uid == 0) + if (ts_UID == 0) s_set_max_slots(s, m.u.max_slots); close(s); remove_connection(index); @@ -598,12 +621,12 @@ static enum Break client_read(int index) { s_get_max_slots(s); break; case SWAP_JOBS: - if (m.uid == 0) { + if (ts_UID == 0) { s_swap_jobs(s, m.u.swap.jobid1, m.u.swap.jobid2); } else { - int job1_uid = s_get_job_uid(m.u.swap.jobid1); - int job2_uid = s_get_job_uid(m.u.swap.jobid2); - if (m.uid == job1_uid && m.uid == job2_uid) { + int job1_uid = s_get_job_tsUID(m.u.swap.jobid1); + int job2_uid = s_get_job_tsUID(m.u.swap.jobid2); + if (ts_UID == job1_uid && ts_UID == job2_uid) { s_swap_jobs(s, m.u.swap.jobid1, m.u.swap.jobid2); } } @@ -656,8 +679,8 @@ static void s_runjob(int jobid, int index) { error("Run job of the client %i which doesn't have any job", index); s = client_cs[index].socket; - fprintf(dbf, "socket = %d, jobid = %d\n", s, jobid); - fflush(dbf); + // fprintf(dbf, "socket = %d, jobid = %d\n", s, jobid); + // fflush(dbf); s_send_runjob(s, jobid); } diff --git a/server_start.c b/server_start.c index 2f80fce..248c2ff 100644 --- a/server_start.c +++ b/server_start.c @@ -174,7 +174,7 @@ void wait_server_up(int fd) { } static void server_info() { - printf("Start tast-spooler server from root[%d]\n", client_uid); + printf("Start tast-spooler server from root[%d]\n", root_UID); printf(" Socket path: %s [TS_SOCKET]\n", socket_path); printf(" Read user file from %s [TS_USER_PATH]\n", get_user_path()); printf(" Write log file to %s [TS_LOGFILE_PATH]\n", set_server_logfile()); @@ -244,13 +244,17 @@ int ensure_server_up(int daemonFlag) { /* If error other than "No one listens on the other end"... */ if (!(errno == ENOENT || errno == ECONNREFUSED)) - error("c: cannot connect to the server"); + error("Error: cannot connect to the server"); if (errno == ECONNREFUSED) unlink(socket_path); + int optval = 1; + if (setsockopt(server_socket, SOL_SOCKET, SO_PASSCRED, &optval, sizeof(optval)) == -1) + error("Error: cannot setup SO_PASSCRED"); + /* Try starting the server */ - if (client_uid == root_UID) { + if (getuid() == root_UID) { if (daemonFlag) { printf("Start task-spooler server as daemon\n"); server_daemon(); diff --git a/user.c b/user.c index a1b1af9..ff6da4e 100644 --- a/user.c +++ b/user.c @@ -114,12 +114,12 @@ void write_logfile(const struct Job *p) { time_t now = time(0); strftime(buf, 100, "%Y-%m-%d %H:%M:%S", localtime(&now)); // snprintf(buf, 1024, "[%d] %s @ %s\n", p->jobid, p->command, date); - int user_id = p->user_id; + int ts_UID = p->ts_UID; char *label = ".."; if (p->label) label = p->label; fprintf(f, "[%d] %s P:%d <%s> Pid: %d CMD: %s @ %s\n", p->jobid, - user_name[user_id], p->num_slots, label, p->pid, p->command, buf); + user_name[ts_UID], p->num_slots, label, p->pid, p->command, buf); fclose(f); } @@ -205,19 +205,19 @@ void read_user_file(const char *path) { printf("error in read %s at line %s", path, line); continue; } else { - int user_id = get_user_id(UID); - if (user_id == -1) { + int ts_UID = get_tsUID(UID); + if (ts_UID == -1) { if (user_number >= USER_MAX) continue; - user_id = user_number; + ts_UID = user_number; user_number++; - user_UID[user_id] = UID; - user_max_slots[user_id] = slots; - strncpy(user_name[user_id], name, USER_NAME_WIDTH - 1); + user_UID[ts_UID] = UID; + user_max_slots[ts_UID] = slots; + strncpy(user_name[ts_UID], name, USER_NAME_WIDTH - 1); } else { - user_max_slots[user_id] = slots; + user_max_slots[ts_UID] = slots; } } } @@ -228,11 +228,11 @@ void read_user_file(const char *path) { } const char *uid2user_name(int uid) { - if (uid == 0) - return "Root"; - int user_id = get_user_id(uid); - if (user_id != -1) { - return user_name[user_id]; + // if (uid == 0) + // return "Root"; + int ts_UID = get_tsUID(uid); + if (ts_UID != -1) { + return user_name[ts_UID]; } else { return "Unknown"; } @@ -254,6 +254,7 @@ void s_user_status_all(int s) { send_list_line(s, buffer); } +// i = ts_UID; void s_user_status(int s, int i) { char buffer[256]; char *extra = ""; @@ -265,7 +266,7 @@ void s_user_status(int s, int i) { send_list_line(s, buffer); } -int get_user_id(int uid) { +int get_tsUID(int uid) { for (int i = 0; i < user_number; i++) { if (uid == user_UID[i]) { return i; diff --git a/user.h b/user.h index 63bf22e..c00d3cb 100644 --- a/user.h +++ b/user.h @@ -1,5 +1,6 @@ #define USER_NAME_WIDTH 256 #define USER_MAX 100 +#include char user_name[USER_MAX][USER_NAME_WIDTH]; int server_uid; @@ -10,4 +11,10 @@ int user_jobs[USER_MAX]; int user_queue[USER_MAX]; int user_locked[USER_MAX]; int user_number; -char *logfile_path; \ No newline at end of file +char *logfile_path; + +struct ucred { + uint32_t pid; + uint32_t uid; + uint32_t gid; +}; \ No newline at end of file From 70eae7ac1960cb06eb27a039991e1b19ac360517 Mon Sep 17 00:00:00 2001 From: kylin Date: Thu, 2 Mar 2023 10:04:46 +0800 Subject: [PATCH 43/91] fixed the bug on the job start time and log file --- client.c | 25 ++++++++++++++++++++++--- execute.c | 2 +- jobs.c | 34 ++++++++++++++++++++++++++-------- main.c | 2 -- main.h | 8 +++++--- server.c | 30 ++++++++++++++---------------- 6 files changed, 68 insertions(+), 33 deletions(-) diff --git a/client.c b/client.c index 0894dc2..5e1cd81 100644 --- a/client.c +++ b/client.c @@ -110,6 +110,14 @@ void c_new_job() { free(myenv); } +static void c_print_line(struct Msg* m) { + char *buffer; + buffer = (char *)malloc(m->u.size); + recv_bytes(server_socket, buffer, m->u.size); + printf("%s", buffer); + free(buffer); +} + int c_wait_newjob_ok() { struct Msg m = default_msg(); int res; @@ -121,9 +129,19 @@ int c_wait_newjob_ok() { fprintf(stderr, "Error, queue full\n"); exit(EXITCODE_QUEUE_FULL); } + + if (m.type == LIST_LINE) { + c_print_line(&m); + return c_wait_newjob_ok(); + } + + if (m.type == NEWJOB_PID_NOK) { + // fprintf(stderr, "Error, queue full\n"); + exit(EXITCODE_RELINK_FAILED); + } if (m.type != NEWJOB_OK) error("Error getting the newjob_ok"); - + return m.jobid; } @@ -598,7 +616,6 @@ void c_kill_all_jobs() { } void c_remove_job() { - printf("c_remove_job()\n"); struct Msg m = default_msg(); int res; char *string = 0; @@ -620,9 +637,11 @@ void c_remove_job() { case LIST_LINE: /* Only ONE line accepted */ string = (char *)malloc(m.u.size); res = recv_bytes(server_socket, string, m.u.size); - fprintf(stderr, "Error in the request: %s", string); if (strncmp(string, "Running job", 11) == 0) { + printf("%s", string); c_kill_job(); + } else { + fprintf(stderr, "Error in the request: %s", string); } free(string); exit(-1); diff --git a/execute.c b/execute.c index 3d60782..1194e07 100644 --- a/execute.c +++ b/execute.c @@ -70,7 +70,7 @@ static void run_relink(int pid, struct Result *result) { signals_child_pid = pid; unblock_sigint_and_install_handler(); // printf("runjob_ok %s\n", ofname); - c_send_runjob_ok(ofname, pid); + c_send_runjob_ok(ofname, -1); wait_for_pid(pid); diff --git a/jobs.c b/jobs.c index c58e93d..6cd0082 100644 --- a/jobs.c +++ b/jobs.c @@ -144,20 +144,24 @@ static struct Job *findjob(int jobid) { } -static int pid_in_Jobs(int pid) { - if (pid == 0) return 0; +static int jobid_from_pid(int pid) { + // if (pid == 0) return 0; struct Job *p = &firstjob; while (p->next != NULL) { p = p->next; - if (p->pid == pid) return 1; + if (p->pid == pid) return p->jobid; } - return 0; + return -1; } // if any error return 0; -int check_relink_pid(int ts_UID, int pid) { - if (pid_in_Jobs(pid) == 1) { +int s_check_relink(int s, struct Msg *m, int ts_UID) { + int pid = m->u.newjob.taskpid; + int jobid = jobid_from_pid(pid); + if (jobid != -1) { + sprintf(buff, " Error: PID [%i] has already in job queue as Jobid: %i\n", pid, jobid); + send_list_line(s, buff); return -1; } @@ -166,6 +170,8 @@ int check_relink_pid(int ts_UID, int pid) { snprintf(filename, 256, "/proc/%d/stat", pid); if (stat(filename, &t_stat) == -1) { + sprintf(buff, " Error: PID [%i] is not running\n", pid); + send_list_line(s, buff); return -1; } @@ -175,6 +181,10 @@ int check_relink_pid(int ts_UID, int pid) { } else if (ts_UID == job_tsUID) { return job_tsUID; } else { + sprintf(buff, " Error: PID [%i] is owned by [%d] `%s` not the user [%d] `%s`\n", + pid, user_UID[job_tsUID], user_name[job_tsUID], + user_UID[ts_UID], user_name[ts_UID]); + send_list_line(s, buff); return -1; } } @@ -727,6 +737,7 @@ int s_newjob(int s, struct Msg *m, int ts_UID) { user_busy[id] += num_slots; user_jobs[id]++; p->state = RELINK; + p->info.start_time.tv_sec = m->u.newjob.start_time; } return p->jobid; @@ -1303,8 +1314,15 @@ int s_remove_job(int s, int *jobid, int client_tsUID) { if (*jobid == -1) snprintf(buff, 255, "The last job cannot be removed.\n"); - else - snprintf(buff, 255, "The job %i cannot be removed.\n", *jobid); + else { + if (p == NULL) { + snprintf(buff, 255, "The job %i is not in queue.\n", *jobid); + } else { + snprintf(buff, 255, "The job %i is owned by [%d] `%s` not the user [%d] `%s`.\n", + *jobid, user_UID[p->ts_UID], user_name[p->ts_UID], + user_UID[client_tsUID], user_name[client_tsUID]); + } + } if (p != NULL) { if (p->ts_UID != client_tsUID) { snprintf(buff, 255, "The job %i belongs to %s not %s.\n", diff --git a/main.c b/main.c index a8fc770..220da2c 100644 --- a/main.c +++ b/main.c @@ -230,7 +230,6 @@ void parse_opts(int argc, char **argv) { command_line.jobid = str2int(optarg); break; case 'r': - printf("c_REMOVEJOB = %s\n", optarg); command_line.request = c_REMOVEJOB; command_line.jobid = str2int(optarg); break; @@ -851,7 +850,6 @@ int main(int argc, char **argv) { case c_REMOVEJOB: if (!command_line.need_server) error("The command %i needs the server", command_line.request); - printf("remove the job\n"); c_remove_job(); break; case c_WAITJOB: diff --git a/main.h b/main.h index 8f3f202..e5ffd75 100644 --- a/main.h +++ b/main.h @@ -46,6 +46,7 @@ enum MsgTypes { GET_VERSION, VERSION, NEWJOB_NOK, + NEWJOB_PID_NOK, COUNT_RUNNING, GET_LABEL, LAST_ID, @@ -222,7 +223,8 @@ struct Job { enum ExitCodes { EXITCODE_OK = 0, EXITCODE_UNKNOWN_ERROR = -1, - EXITCODE_QUEUE_FULL = 2 + EXITCODE_QUEUE_FULL = 2, + EXITCODE_RELINK_FAILED = 3 }; /* main.c */ @@ -518,7 +520,7 @@ void check_running_task(int pid); int user_locker; time_t locker_time; int jobsort_flag; -FILE* dbf; // # DEBUG +// FILE* dbf; // # DEBUG int check_ifsleep(int pid); int check_running_dead(int jobid); @@ -538,7 +540,7 @@ void s_unlock_server(int s, int uid); int s_check_locker(int s, int uid); void s_set_jobids(int i); void s_sort_jobs(); -int check_relink_pid(int uid, int pid); +int s_check_relink(int s, struct Msg *m, int ts_UID); /* client.c */ diff --git a/server.c b/server.c index f8e692b..5bb7335 100644 --- a/server.c +++ b/server.c @@ -171,9 +171,9 @@ static int get_max_descriptors() { void server_main(int notify_fd, char *_path) { - dbf = fopen("/home/kylin/task-spooler/debug.txt", "w"); - fprintf(dbf, "start server_main\n"); - fflush(dbf); + // dbf = fopen("/home/kylin/task-spooler/debug.txt", "w"); + // fprintf(dbf, "start server_main\n"); + // fflush(dbf); int ls; struct sockaddr_un addr; @@ -321,8 +321,6 @@ static void server_loop(int ls) { } /* This will return firstjob->jobid or -1 */ newjob = next_run_job(); - fprintf(dbf, "start new job: %d\n", newjob); - fflush(dbf); if (newjob != -1) { int conn, awaken_job; conn = get_conn_of_jobid(newjob); @@ -407,13 +405,9 @@ static enum Break client_read(int index) { res = recv_msg(s, &m); if (res == -1) { warning("client recv failed"); - fprintf(dbf, "start clean_after_clieeared\n"); - fflush(dbf); clean_after_client_disappeared(s, index); return NOBREAK; } else if (res == 0) { - fprintf(dbf, "start clean_after_clie2\n"); - fflush(dbf); clean_after_client_disappeared(s, index); return NOBREAK; } @@ -488,16 +482,19 @@ static enum Break client_read(int index) { case NEWJOB: if (m.u.newjob.taskpid != 0) { // check if taskpid isnot in queue and from a valid user. - fprintf(dbf, "ts_UID = %d, taskpid = %d\n", ts_UID, m.u.newjob.taskpid); - ts_UID = ts(ts_UID, m.u.newjob.taskpid); - // fprintf(dbf, "ts_UID = %d, taskpid = %d\n", ts_UID, m.u.newjob.taskpid); - // fflush(dbf); + ts_UID = s_check_relink(s, &m, ts_UID); } else { if (s_check_locker(s, ts_UID) == 1) { break; } } - if (ts_UID < 0 || ts_UID > USER_MAX) { break; } - + if (ts_UID < 0 || ts_UID > USER_MAX) { + struct Msg m = default_msg(); + m.type = NEWJOB_PID_NOK; + send_msg(s, &m); + // close(s); + break; + } + client_cs[index].jobid = s_newjob(s, &m, ts_UID); client_cs[index].hasjob = 1; if (!job_is_holding_client(client_cs[index].jobid)) @@ -516,7 +513,8 @@ static enum Break client_read(int index) { if (res != m.u.output.ofilename_size) error("Reading the ofilename"); } - s_process_runjob_ok(client_cs[index].jobid, buffer, m.u.output.pid); + if (m.u.output.pid > 0) + s_process_runjob_ok(client_cs[index].jobid, buffer, m.u.output.pid); } break; case KILL_ALL: if (ts_UID == 0) From 1431e4341cb0a5b0121a2fa88d809615add5039c Mon Sep 17 00:00:00 2001 From: kylin Date: Mon, 6 Mar 2023 12:43:05 +0800 Subject: [PATCH 44/91] add check_daemon and remove f-string in python --- jobs.c | 2 ++ main.c | 13 +++++++++++++ main.h | 2 ++ relink.py | 9 +++++---- server_start.c | 22 ++++++++++++++++++++++ 5 files changed, 44 insertions(+), 4 deletions(-) diff --git a/jobs.c b/jobs.c index 6cd0082..a5264b7 100644 --- a/jobs.c +++ b/jobs.c @@ -738,6 +738,8 @@ int s_newjob(int s, struct Msg *m, int ts_UID) { user_jobs[id]++; p->state = RELINK; p->info.start_time.tv_sec = m->u.newjob.start_time; + p->info.start_time.tv_usec = 0; + } return p->jobid; diff --git a/main.c b/main.c index 220da2c..7a8e872 100644 --- a/main.c +++ b/main.c @@ -144,6 +144,7 @@ static struct option longOptions[] = { {"daemon", no_argument, NULL, 0}, {"pid", required_argument, NULL, 0}, {"stime", required_argument, NULL, 0}, + {"check_daemon", no_argument, NULL, 0}, {NULL, 0, NULL, 0}}; void parse_opts(int argc, char **argv) { @@ -166,6 +167,9 @@ void parse_opts(int argc, char **argv) { command_line.request = c_GET_LOGDIR; } else if (strcmp(longOptions[optionIdx].name, "daemon") == 0) { command_line.request = c_DAEMON; + } else if (strcmp(longOptions[optionIdx].name, "check_daemon") == 0) { + command_line.request = c_CHECK_DAEMON; + command_line.need_server = 0; } else if (strcmp(longOptions[optionIdx].name, "set_logdir") == 0) { command_line.request = c_SET_LOGDIR; command_line.label = optarg; /* reuse this variable */ @@ -545,6 +549,7 @@ static void print_help(const char *cmd) { "added, if not specified.\n"); printf(" --full_cmd || -F [id] show full command. Of the last " "added, if not specified.\n"); + printf(" --check_daemon Check the daemon is running or not."); printf( " --count_running || -R return the number of running jobs\n"); printf( @@ -666,6 +671,10 @@ int main(int argc, char **argv) { /* This will be inherited by the server, if it's run */ ignore_sigpipe(); + if (command_line.request == c_CHECK_DAEMON) { + c_check_daemon(); + } + if (command_line.need_server) { if (command_line.request == c_DAEMON) { ensure_server_up(1); @@ -674,6 +683,8 @@ int main(int argc, char **argv) { } c_check_version(); } + + switch (command_line.request) { case c_REFRESH_USER: @@ -687,6 +698,8 @@ int main(int argc, char **argv) { break; case c_DAEMON: break; + case c_CHECK_DAEMON: + break; case c_HOLD_JOB: c_hold_job(command_line.jobid); c_wait_server_lines(); diff --git a/main.h b/main.h index e5ffd75..b13c3ab 100644 --- a/main.h +++ b/main.h @@ -66,6 +66,7 @@ enum Request { c_LIST, c_LIST_ALL, c_DAEMON, + c_CHECK_DAEMON, c_REFRESH_USER, c_STOP_USER, c_CONT_USER, @@ -551,3 +552,4 @@ void c_hold_job(int jobid); int c_lock_server(); int c_unlock_server(); void c_restart_job(int jobid); +void c_check_daemon(); diff --git a/relink.py b/relink.py index 075d3ed..8a30e3f 100755 --- a/relink.py +++ b/relink.py @@ -5,7 +5,8 @@ import os logfile = "/home/kylin/task-spooler/log.txt" -days_num = 1000 +ts_CMD="ts" +days_num = 30 if len(sys.argv) != 1: days_num = int(sys.argv[2]); exit(1) @@ -36,7 +37,7 @@ def parse(s): t_now = datetime.datetime.now() # t_line = time.gmtime() t_line = t_now - datetime.timedelta(days = days_num) -print(f" only restore tasks with {days_num} days, start by", t_line) +print(" only restore tasks with {} days, start by".format(days_num), t_line) tasks = [] for l in lines[:]: user, procs, pid, tag, CMD, t_time = parse(l) @@ -56,10 +57,10 @@ def parse(s): for i in tasks[:]: if i[0] == "..": - CMD = 'ts --pid {} -N {} "{}"'.format(*i[1:]) + CMD = '{} --pid {} -N {} "{}"'.format(ts_CMD, *i[1:]) # CMD = 'ts --pid {} -N {} --stime {:} "{}"'.format(*i[1:]) else: - CMD = 'ts -L {} --pid {} -N {} "{}"'.format(*i) + CMD = '{} -L {} --pid {} -N {} "{}"'.format(ts_CMD, *i) # CMD = 'ts -L {} --pid {} -N {} --stime {:} "{}"'.format(*i) print(CMD) os.system(CMD) diff --git a/server_start.c b/server_start.c index 248c2ff..739a75c 100644 --- a/server_start.c +++ b/server_start.c @@ -150,6 +150,28 @@ int try_connect(int s) { return res; } +void c_check_daemon() { + int res; + server_socket = socket(AF_UNIX, SOCK_STREAM, 0); + if (server_socket == -1) + error("getting the server socket"); + + create_socket_path(&socket_path); + + res = try_connect(server_socket); + + /* Good connection */ + if (res == 0) { + printf("Good connection to the task-spooler server\n"); + exit(0); + } else { + printf("Cannot connect to the task-spooler server\n"); + exit(1); + } +} + + + static void try_check_ownership() { int res; struct stat socketstat; From 8bd7b1ea143576c8383a6658653c492f514ce0c8 Mon Sep 17 00:00:00 2001 From: kylin Date: Fri, 24 Mar 2023 00:32:11 +0800 Subject: [PATCH 45/91] allow to release the resource for the holdon jobs --- client.c | 6 +++++- jobs.c | 15 ++++++++++++++- main.c | 2 +- user.h | 14 +++++++------- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/client.c b/client.c index 5e1cd81..6ede153 100644 --- a/client.c +++ b/client.c @@ -179,6 +179,7 @@ int c_wait_server_commands() { return -1; } +// check weather the return is line message with a specific header [pre_str]. static int wait_server_lines_and_check(const char *pre_str) { struct Msg m = default_msg(); int res; @@ -507,12 +508,15 @@ void c_restart_job(int jobid) { // printf("kill the pid: %d\n", pid); /* Send SIGTERM to the process group, as pid is for process group */ // kill(-pid, SIGCONT); - kill_pid(pid, "CONT"); struct Msg m = default_msg(); m.type = RESTART_JOB; m.jobid = jobid; send_msg(server_socket, &m); + // not error, restart job + if (wait_server_lines_and_check("Error") == 0) { + kill_pid(pid, "CONT"); + } } int c_tail() { diff --git a/jobs.c b/jobs.c index a5264b7..74581f4 100644 --- a/jobs.c +++ b/jobs.c @@ -1511,6 +1511,10 @@ void s_hold_job(int s, int jobid, int ts_UID) { if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { // kill(p->pid, SIGSTOP); // kill_pid(p->pid, "-stop"); + user_busy[ts_UID] -= p->num_slots; + busy_slots -= p->num_slots; + user_queue[ts_UID]--; + user_jobs[ts_UID]--; snprintf(buff, 255, "Hold on job [%d] successfully!\n", jobid); } else { @@ -1534,7 +1538,16 @@ void s_restart_job(int s, int jobid, int ts_UID) { if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { // kill(p->pid, SIGCONT); // kill_pid(p->pid, "-cont"); - snprintf(buff, 255, "Restart job [%d] successfully!\n", jobid); + int num_slots = p->num_slots; + if (user_busy[ts_UID] + num_slots < user_max_slots[ts_UID] && busy_slots + num_slots <= max_slots) { + user_busy[ts_UID] += num_slots; + busy_slots += num_slots; + user_queue[ts_UID]++; + user_jobs[ts_UID]++; + snprintf(buff, 255, "Restart job [%d] successfully!\n", jobid); + } else { + snprintf(buff, 255, "Error: not enough slots [%d]\n", jobid); + } } else { snprintf(buff, 255, "Error: cannot hold on job [%d]\n", jobid); } diff --git a/main.c b/main.c index 7a8e872..210076a 100644 --- a/main.c +++ b/main.c @@ -707,7 +707,7 @@ int main(int argc, char **argv) { break; case c_RESTART_JOB: c_restart_job(command_line.jobid); - c_wait_server_lines(); + // c_wait_server_lines(); break; case c_LOCK_SERVER: errorlevel = c_lock_server(); diff --git a/user.h b/user.h index c00d3cb..4bc2747 100644 --- a/user.h +++ b/user.h @@ -2,14 +2,14 @@ #define USER_MAX 100 #include -char user_name[USER_MAX][USER_NAME_WIDTH]; +char user_name[USER_MAX][USER_NAME_WIDTH]; // the linux user name int server_uid; -int user_max_slots[USER_MAX]; -int user_UID[USER_MAX]; -int user_busy[USER_MAX]; -int user_jobs[USER_MAX]; -int user_queue[USER_MAX]; -int user_locked[USER_MAX]; +int user_max_slots[USER_MAX]; // the max slots for each user in TS +int user_UID[USER_MAX]; // the linux UID for each user in TS +int user_busy[USER_MAX]; // the number of used slots +int user_jobs[USER_MAX]; // the number of job in running +int user_queue[USER_MAX]; // the number of job in queue +int user_locked[USER_MAX]; // whether the user is locked int user_number; char *logfile_path; From cfbc6cbae7e0750c5857ccf70699dbea713e6c44 Mon Sep 17 00:00:00 2001 From: kylin Date: Sat, 25 Mar 2023 15:33:53 +0800 Subject: [PATCH 46/91] add the sqlite3 to log tasks --- Makefile | 6 +- client.c | 30 +++-- error.c | 1 + execute.c | 9 +- jobs.c | 307 +++++++++++++++++++++++++++++++++++++++++++------ list.c | 32 +++--- main.c | 39 ++++--- main.h | 50 +++++--- server.c | 97 ++++++++++++---- server_start.c | 2 + user.c | 30 ++++- 11 files changed, 479 insertions(+), 124 deletions(-) diff --git a/Makefile b/Makefile index c43e461..abeeb1e 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,8 @@ OBJECTS=main.o \ info.o \ env.o \ tail.o \ - user.o + user.o \ + sqlite.o TARGET=ts INSTALL=install -c @@ -28,7 +29,7 @@ GIT_REPO=$(shell git rev-parse --is-inside-work-tree) all: $(TARGET) $(TARGET): $(OBJECTS) - $(CC) $(LDFLAGS) -o $(TARGET) $^ + $(CC) $(LDFLAGS) -o $(TARGET) $^ -lsqlite3 %.o : %.c $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ @@ -52,6 +53,7 @@ error.o: error.c main.h signals.o: signals.c main.h list.o: list.c main.h tail.o: tail.c main.h +sqlite.o: sqlite.c main.h clean: rm -f *.o $(TARGET); killall ts; diff --git a/client.c b/client.c index 6ede153..3cf3c34 100644 --- a/client.c +++ b/client.c @@ -23,15 +23,15 @@ static void c_wait_job_send(); static void c_wait_running_job_send(); char *build_command_string() { + return charArray_string(command_line.command.num, command_line.command.array); +} + +char *charArray_string(int num, char** array) { int size; int i; - int num; - char **array; char *commandstring; size = 0; - num = command_line.command.num; - array = command_line.command.array; /* Count bytes needed */ for (i = 0; i < num; ++i) { @@ -58,17 +58,22 @@ char *build_command_string() { void c_new_job() { struct Msg m = default_msg(); - char *new_command; + char *new_command, path[1024]; char *myenv; m.type = NEWJOB; - new_command = build_command_string(); + getcwd(path, 1024); + new_command = command_line.linux_cmd; // build_command_string(); + char* old_command = build_command_string(); myenv = get_environment(); /* global */ + m.jobid = command_line.jobid; m.u.newjob.command_size = strlen(new_command) + 1; /* add null */ + m.u.newjob.command_size_strip = strlen(new_command) - strlen(old_command); + m.u.newjob.path_size = strlen(path) + 1; /* add null */ if (myenv) m.u.newjob.env_size = strlen(myenv) + 1; /* add null */ else @@ -100,6 +105,9 @@ void c_new_job() { /* Send the command */ send_bytes(server_socket, new_command, m.u.newjob.command_size); + /* Send the work dir */ + send_bytes(server_socket, path, m.u.newjob.path_size); + /* Send the label */ send_bytes(server_socket, command_line.label, m.u.newjob.label_size); @@ -126,7 +134,7 @@ int c_wait_newjob_ok() { if (res == -1) error("Error in wait_newjob_ok"); if (m.type == NEWJOB_NOK) { - fprintf(stderr, "Error, queue full\n"); + fprintf(stderr, "Error, queue full or conflict jobid\n"); exit(EXITCODE_QUEUE_FULL); } @@ -474,7 +482,7 @@ static char *get_output_file(int *pid) { return 0; } -void c_hold_job(int jobid) { +void c_pause_job(int jobid) { int pid = 0; /* This will exit if there is any error */ get_output_file(&pid); @@ -490,12 +498,12 @@ void c_hold_job(int jobid) { kill_pid(pid, "STOP"); struct Msg m = default_msg(); - m.type = HOLD_JOB; + m.type = PAUSE_JOB; m.jobid = jobid; send_msg(server_socket, &m); } -void c_restart_job(int jobid) { +void c_rerun_job(int jobid) { int pid = 0; /* This will exit if there is any error */ get_output_file(&pid); @@ -510,7 +518,7 @@ void c_restart_job(int jobid) { // kill(-pid, SIGCONT); struct Msg m = default_msg(); - m.type = RESTART_JOB; + m.type = RERUN_JOB; m.jobid = jobid; send_msg(server_socket, &m); // not error, restart job diff --git a/error.c b/error.c index 7911e21..457539b 100644 --- a/error.c +++ b/error.c @@ -155,6 +155,7 @@ void error(const char *str, ...) { } problem(ERROR, str, ap); + close_sqlite(); exit(-1); } diff --git a/execute.c b/execute.c index 1194e07..f0cff27 100644 --- a/execute.c +++ b/execute.c @@ -74,7 +74,7 @@ static void run_relink(int pid, struct Result *result) { wait_for_pid(pid); - command = build_command_string(); + command = command_line.linux_cmd; // build_command_string(); if (command_line.send_output_by_mail) { send_mail(command_line.jobid, result->errorlevel, ofname, command); } @@ -148,7 +148,7 @@ static void run_parent(int fd_read_filename, int pid, struct Result *result) { result->errorlevel = -1; } - command = build_command_string(); + command = command_line.linux_cmd; // build_command_string(); if (command_line.send_output_by_mail) { send_mail(command_line.jobid, result->errorlevel, ofname, command); } @@ -320,9 +320,8 @@ int run_job(int jobid, struct Result *res) { int pid; int errorlevel = 0; int p[2]; - char path[256]; - getcwd(path, 256); - printf("start run_job()\n"); + char path[1024]; + getcwd(path, 1024); // const char *tmpdir = get_logdir(); // printf("tmpdir: %s\n", tmpdir); diff --git a/jobs.c b/jobs.c index 74581f4..6115aa9 100644 --- a/jobs.c +++ b/jobs.c @@ -49,16 +49,22 @@ static struct Job *get_job(int jobid); void notify_errorlevel(struct Job *p); -void s_set_jobids(int i) { jobids = i; } +void s_set_jobids(int i) { + jobids = i; + set_jobids_DB(i); +} static void destroy_job(struct Job *p) { - free(p->notify_errorlevel_to); - free(p->command); - free(p->output_filename); - pinfo_free(&p->info); - free(p->depend_on); - free(p->label); - free(p); + if (p != NULL) { + free(p->notify_errorlevel_to); + free(p->command); + free(p->work_dir); + free(p->output_filename); + pinfo_free(&p->info); + free(p->depend_on); + free(p->label); + free(p); + } } void send_list_line(int s, const char *str) { @@ -155,6 +161,14 @@ static int jobid_from_pid(int pid) { return -1; } +// return 1 for running, other is dead +int s_check_running_pid(int pid) { + char cmd[256], filename[256] = ""; + snprintf(cmd, sizeof(cmd), "readlink -f /proc/%d/fd/1", pid); + linux_cmd(cmd, filename, sizeof(filename)); + return strlen(filename) != 0; +} + // if any error return 0; int s_check_relink(int s, struct Msg *m, int ts_UID) { int pid = m->u.newjob.taskpid; @@ -413,6 +427,9 @@ const char *jstate2string(enum Jobstate s) { case RELINK: jobstate = "relink"; break; + case WAIT: + jobstate = "wait"; + break; } return jobstate; } @@ -438,8 +455,6 @@ void s_list(int s, int ts_UID) { buffer = joblist_line(p); // sprintf(buf, "== jobid = %d\n", p->jobid); // send_list_line(s, buf); - // fprintf(dbf, "----- %s\n", buffer); - // fflush(dbf); send_list_line(s, buffer); free(buffer); } @@ -532,8 +547,19 @@ static struct Job *newjobptr() { p->next = (struct Job *)malloc(sizeof(*p)); p->next->next = 0; p->next->output_filename = 0; - p->next->command = 0; p->next->pid = 0; + p->next->command = NULL; + p->next->work_dir = NULL; + p->next->command_strip = 0; + + struct Procinfo* info= &(p->next->info); + info->enqueue_time.tv_sec = 0; + info->start_time.tv_sec = 0; + info->end_time.tv_sec = 0; + info->enqueue_time.tv_usec = 0; + info->start_time.tv_usec = 0; + info->end_time.tv_usec = 0; + return p->next; } @@ -568,19 +594,35 @@ static int find_last_stored_jobid_finished() { } /* Returns job id or -1 on error */ -int s_newjob(int s, struct Msg *m, int ts_UID) { +int s_newjob(int s, struct Msg *m, int ts_UID, int socket) { struct Job *p; int res; + int waitjob_flag = 0; + if (m->jobid != 0) { + p = findjob(m->jobid); + if (p != NULL && p->state == WAIT) { + jobDB_wait_num--; + waitjob_flag = 1; + p->state = QUEUED; + } else { + return -1; + } + } - p = newjobptr(); - - p->jobid = jobids++; - if (count_not_finished_jobs() < max_jobs) - p->state = QUEUED; - else - p->state = HOLDING_CLIENT; - + if (waitjob_flag == 0) { + p = newjobptr(); + if (m->jobid != 0) { + p->jobid = m->jobid; + jobids = jobids > m->jobid ? jobids : m->jobid + 1; + } else { + p->jobid = jobids++; + } + if (count_not_finished_jobs() < max_jobs) { + p->state = QUEUED; + } else + p->state = HOLDING_CLIENT; + } // save the ts_UID and record the number of waiting jobs p->ts_UID = ts_UID; // get_tsUID(m->uid); user_queue[p->ts_UID]++; @@ -690,14 +732,36 @@ int s_newjob(int s, struct Msg *m, int ts_UID) { pinfo_set_enqueue_time(&p->info); /* load the command */ - p->command = malloc(m->u.newjob.command_size); - if (p->command == 0) + + char* buff = malloc(m->u.newjob.command_size); + if (buff == 0) error("Cannot allocate memory in s_newjob command_size (%i)", m->u.newjob.command_size); - res = recv_bytes(s, p->command, m->u.newjob.command_size); + res = recv_bytes(s, buff, m->u.newjob.command_size); if (res == -1) error("wrong bytes received"); + if (1) { // waitjob_flag == 0 + p->command = buff; + p->command_strip = m->u.newjob.command_size_strip; + } else { + free(buff); + } + + /* load the work dir */ + p->work_dir = 0; + if (m->u.newjob.path_size > 0) { + char *ptr; + ptr = (char *)malloc(m->u.newjob.path_size); + if (ptr == 0) + error("Cannot allocate memory in s_newjob path_size(%i)", + m->u.newjob.path_size); + res = recv_bytes(s, ptr, m->u.newjob.path_size); + if (res == -1) + error("wrong bytes received"); + p->work_dir = ptr; + } + /* load the label */ p->label = 0; if (m->u.newjob.label_size > 0) { @@ -741,7 +805,8 @@ int s_newjob(int s, struct Msg *m, int ts_UID) { p->info.start_time.tv_usec = 0; } - + if(waitjob_flag == 0) insert_DB(p, "Jobs"); + set_jobids_DB(jobids); return p->jobid; } @@ -880,6 +945,9 @@ static void new_finished_job(struct Job *j) { } p->next = j; p->next->next = 0; + + delete_DB(j->jobid, "Jobs"); + insert_DB(j, "Finished"); } static int job_is_in_state(int jobid, enum Jobstate state) { @@ -973,6 +1041,162 @@ void job_finished(const struct Result *result, int jobid) { jpointer->next = newfirst; } } +static int fork_cmd(int UID, const char* path, const char* cmd) { + int pid = -1; //定义一个进程ID变量 + int fd[2]; //定义一个管道数组 + // char buffer[10]; //定义一个缓冲区 + if (pipe(fd) < 0) //创建一个管道 + { + perror("pipe error"); //打印错误信息 + return -1; + } + pid = fork(); //调用fork()函数创建子进程 + if (pid < 0) //如果返回值小于0,表示fork失败 + { + perror("fork error"); //打印错误信息 + return -1; + } + else if (pid == 0) //如果返回值等于0,表示子进程正在运行 + { + // printf("This is child process, pid = %d\n", getpid()); //打印子进程的ID + close(fd[0]); //关闭管道的读端 + /* + if (setuid(UID) < 0) { + sprintf(buffer, "%d", -1); //将子进程的ID转换为字符串 + } else { + sprintf(buffer, "%d", getpid()); //将子进程的ID转换为字符串 + } + write(fd[1], buffer, sizeof(buffer)); //将字符串写入管道的写端 + */ + setuid(UID); + close(fd[1]); //关闭管道的写端 + + if (path != NULL) chdir(path); + system(cmd); + exit(0); + /* + int cmd_array_size; + printf("cmd = %s\n", cmd); + char** cmd_arry = split_str(cmd, &cmd_array_size); + if (cmd_array_size > 0) { + printf("run cmd %s\n", cmd_arry[0]); + system(cmd); + exit(0); + // execvp(cmd_arry[0], cmd_arry); + } + // execlp("ls", "-l", NULL); //执行ls -l命令,替换当前进程 + */ + return -1; + } + else //如果返回值大于0,表示父进程正在运行 + { + // printf("This is parent process, pid = %d\n", getpid()); //打印父进程的ID + // close(fd[1]); //关闭管道的写端 + // read(fd[0], buffer, sizeof(buffer)); //从管道的读端读取字符串 + printf("[Child PID:%d] Add queued job: %s\n", pid, cmd); //打印子进程的ID + // close(fd[0]); //关闭管道的读端 + //wait(NULL); //等待子进程结束 + } + return pid; +} +// TODO add the check of running state. +static void s_add_job(struct Job* j, struct Job** p) { + if (j->state == RUNNING || j->state == HOLDING_CLIENT || j->state == RELINK) { + if (j->pid > 0 && s_check_running_pid(j->pid) == 1) { + printf("add job %d\n", j->jobid); + + int ts_UID = j->ts_UID; + user_jobs[ts_UID]++; + + if (j->state == RUNNING) { + int slots = j->num_slots; + user_busy[ts_UID] += slots; + busy_slots += slots; + } + + jobDB_Jobs[jobDB_num] = j; + jobDB_num++; + (*p)->next = j; + (*p) = j; + + jobids = jobids > j->jobid ? jobids : j->jobid + 1; + j = NULL; + } + } else if (j->state == QUEUED) { + printf("add the queue job %d\n", j->jobid); + j->state = WAIT; + jobDB_wait_num++; + (*p)->next = j; + (*p) = j; + + + + char c[32]; //创建一个存储数字的字符串 + sprintf(c, "-J %d ", j->jobid); //将数字转换为字符串 + int len = strlen(j->command) + strlen(c) + 1; //计算新字符串的长度 + char *str = (char *)calloc(0, len * sizeof(char)); //用malloc函数分配内存 + if (str == NULL) //判断是否分配成功 + { + printf("Memory allocation failed.\n"); + return; + } + strncpy(str, j->command, j->command_strip); //将s数组的前t个字符复制到str中 + strcat(str, c); //将数字字符串连接到str后面 + strcat(str, (j->command + j->command_strip)); //将s剩余的字符串连接到str后面 + fork_cmd(user_UID[j->ts_UID], j->work_dir, str); + jobids = jobids > j->jobid ? jobids : j->jobid + 1; + j = NULL; + + /* + printf("add job %d; CMD = %s\n", j->jobid, j->command); + jobDB_Jobs[jobDB_num] = j; + jobDB_num++; + user_queue[j->ts_UID]++; + (*p)->next = j; + (*p) = j; + */ + } + + destroy_job(j); +} + +void s_read_sqlite() { + int num_jobs, *jobs_DB = NULL; + struct Job *job, *p; + p = &firstjob; + num_jobs = read_jobid_DB(&(jobs_DB), "Jobs"); + // printf("read from jobs %d\n", num_jobs); + jobDB_Jobs = (struct Job**)malloc(sizeof(struct Job*) * num_jobs); + printf("Jobs:\n"); + for (int i = 0; i < num_jobs; i++) { + job = read_DB(jobs_DB[i], "Jobs"); + if (job == NULL) { + printf("Error in reading DB %d\n", jobs_DB[i]); + } else { + s_add_job(job, &p); + } + } + p->next = NULL; + // clear_DB("Jobs"); + + // finished jobs + p = &first_finished_job; + num_jobs = read_jobid_DB(&(jobs_DB), "Finished"); + printf("Finished:\n"); + for (int i = 0; i < num_jobs; i++) { + job = read_DB(jobs_DB[i], "Finished"); + if (job == NULL) { + printf("Error in reading DB %d\n", jobs_DB[i]); + } else { + printf("add job: %d from %d\n", job->jobid, jobs_DB[i]); + p->next = job; + p = job; + } + } + p->next = NULL; + free(jobs_DB); + set_jobids_DB(jobids); +} void s_clear_finished(int ts_UID) { struct Job *p, *other_user_job = &first_finished_job; @@ -985,6 +1209,7 @@ void s_clear_finished(int ts_UID) { struct Job *tmp; tmp = p->next; if (p->ts_UID == ts_UID || ts_UID == 0) { + delete_DB(p->jobid, "Finished"); destroy_job(p); } else { other_user_job->next = p; @@ -1006,6 +1231,7 @@ void s_process_runjob_ok(int jobid, char *oname, int pid) { p->pid = pid; p->output_filename = oname; pinfo_set_start_time(&p->info); + insert_or_replace_DB(p, "Jobs"); write_logfile(p); } @@ -1362,7 +1588,7 @@ int s_remove_job(int s, int *jobid, int client_tsUID) { */ /* Return the jobid found */ *jobid = p->jobid; - + delete_DB(p->jobid, "Jobs"); /* Tricks for the check_notify_list */ p->state = FINISHED; p->result.errorlevel = -1; @@ -1498,7 +1724,7 @@ void s_unlock_server(int s, int ts_UID) { send_list_line(s, buff); } -void s_hold_job(int s, int jobid, int ts_UID) { +void s_pause_job(int s, int jobid, int ts_UID) { struct Job *p; p = findjob(jobid); if (p == 0) { @@ -1506,7 +1732,11 @@ void s_hold_job(int s, int jobid, int ts_UID) { send_list_line(s, buff); return; } - + if (check_ifsleep(p->pid) == 1) { + snprintf(buff, 255, "job [%d] is aleady in PAUSE.\n", jobid); + send_list_line(s, buff); + return; + } int job_tsUID = p->ts_UID; if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { // kill(p->pid, SIGSTOP); @@ -1515,25 +1745,30 @@ void s_hold_job(int s, int jobid, int ts_UID) { busy_slots -= p->num_slots; user_queue[ts_UID]--; user_jobs[ts_UID]--; - snprintf(buff, 255, "Hold on job [%d] successfully!\n", jobid); + snprintf(buff, 255, "To pause job [%d] successfully!\n", jobid); } else { - snprintf(buff, 255, "Error: cannot hold on job [%d]\n", jobid); + snprintf(buff, 255, "Error: cannot pause job [%d]\n", jobid); } send_list_line(s, buff); + } -void s_restart_job(int s, int jobid, int ts_UID) { +void s_rerun_job(int s, int jobid, int ts_UID) { struct Job *p; p = findjob(jobid); if (p == 0) { snprintf(buff, 255, "Error: cannot find job [%d]\n", jobid); send_list_line(s, buff); - return; } - + + if (check_ifsleep(p->pid) == 0) { + snprintf(buff, 255, "job [%d] is aleady in RUNNING.\n", jobid); + send_list_line(s, buff); + return; + } int job_tsUID = p->ts_UID; if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { // kill(p->pid, SIGCONT); @@ -1544,12 +1779,12 @@ void s_restart_job(int s, int jobid, int ts_UID) { busy_slots += num_slots; user_queue[ts_UID]++; user_jobs[ts_UID]++; - snprintf(buff, 255, "Restart job [%d] successfully!\n", jobid); + snprintf(buff, 255, "To rerun job [%d] successfully!\n", jobid); } else { snprintf(buff, 255, "Error: not enough slots [%d]\n", jobid); } } else { - snprintf(buff, 255, "Error: cannot hold on job [%d]\n", jobid); + snprintf(buff, 255, "Error: cannot rerun job [%d]\n", jobid); } send_list_line(s, buff); } @@ -1741,6 +1976,7 @@ void s_get_max_slots(int s) { send_msg(s, &m); } +/* move jobid upto the top of list */ void s_move_urgent(int s, int jobid) { struct Job *p = 0; struct Job *tmp1; @@ -1778,6 +2014,7 @@ void s_move_urgent(int s, int jobid) { tmp1->next = p->next; p->next = firstjob.next; firstjob.next = p; + movetop_DB(jobid); send_urgent_ok(s); } @@ -1804,7 +2041,7 @@ void s_swap_jobs(int s, int jobid1, int jobid2) { tmp = p1->next; p1->next = p2->next; p2->next = tmp; - + swap_DB(jobid1, jobid2); send_swap_jobs_ok(s); } diff --git a/list.c b/list.c index 12e995f..ce27f39 100644 --- a/list.c +++ b/list.c @@ -114,8 +114,6 @@ static const char *ofilename_shown(const struct Job *p) { } static char *print_noresult(const struct Job *p) { - // fprintf(dbf, "start print_noresult Jobid = %d\n", p->jobid); - // fflush(dbf); const char *jobstate; const char *output_filename; int maxlen; @@ -130,7 +128,7 @@ static char *print_noresult(const struct Job *p) { jobstate = "N/A"; } else { if (check_ifsleep(p->pid) == 1) { - jobstate = "holdon"; + jobstate = "pause"; } } } @@ -181,24 +179,22 @@ static char *print_noresult(const struct Job *p) { error("Malloc for %i failed.\n", maxlen); cmd_len = max((strlen(p->command) + (term_width - maxlen)), 20); - char *cmd = shorten(p->command, cmd_len); + char *cmd = shorten(p->command + p->command_strip, cmd_len); if (p->label) { char *label = shorten(p->label, 10); - snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-20s | %s\n", + snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-21s | %s\n", p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, output_filename); free(label); free(cmd); } else { - char *cmd = shorten(p->command, cmd_len); + char *cmd = shorten(p->command + p->command_strip, cmd_len); char *label = "(..)"; - snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-20s| %s\n", + snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-21s | %s\n", p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, output_filename); free(cmd); } - // fprintf(dbf, line); - // fflush(dbf); return line; } @@ -251,18 +247,18 @@ static char *print_result(const struct Job *p) { error("Malloc for %i failed.\n", maxlen); cmd_len = max((strlen(p->command) + (term_width - maxlen)), 20); - char *cmd = shorten(p->command, cmd_len); + char *cmd = shorten(p->command + p->command_strip, cmd_len); if (p->label) { char *label = shorten(p->label, 10); - snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-20s | %s\n", + snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-21s | %s\n", p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, output_filename); free(label); free(cmd); } else { - char *cmd = shorten(p->command, cmd_len); + char *cmd = shorten(p->command + p->command_strip, cmd_len); char *label = "(..)"; - snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-20s | %s\n", + snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-21s | %s\n", p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, output_filename); free(cmd); @@ -314,10 +310,10 @@ static char *plainprint_noresult(const struct Job *p) { if (p->label) snprintf(line, maxlen, "%i\t%s\t%s\t%s\t%s\t%s\t[%s]\t%s\n", p->jobid, jobstate, output_filename, "", "", dependstr, p->label, - p->command); + p->command + p->command_strip); else snprintf(line, maxlen, "%i\t%s\t%s\t%s\t%s\t%s\t\t%s\n", p->jobid, jobstate, - output_filename, "", "", dependstr, p->command); + output_filename, "", "", dependstr, p->command + p->command_strip); return line; } @@ -367,11 +363,11 @@ static char *plainprint_result(const struct Job *p) { if (p->label) snprintf(line, maxlen, "%i\t%s\t%s\t%i\t%.2f\t%s\t%s\t[%s]\t%s\n", p->jobid, jobstate, output_filename, p->result.errorlevel, real_ms, unit, - dependstr, p->label, p->command); + dependstr, p->label, p->command + p->command_strip); else snprintf(line, maxlen, "%i\t%s\t%s\t%i\t%.2f\t%s\t%s\t\t%s\n", p->jobid, jobstate, output_filename, p->result.errorlevel, real_ms, unit, - dependstr, p->command); + dependstr, p->command + p->command_strip); return line; } @@ -408,7 +404,7 @@ char *joblistdump_torun(const struct Job *p) { if (line == NULL) error("Malloc for %i failed.\n", maxlen); - snprintf(line, maxlen, "ts %s\n", p->command); + snprintf(line, maxlen, "%s\n", p->command); return line; } diff --git a/main.c b/main.c index 210076a..15ecf16 100644 --- a/main.c +++ b/main.c @@ -60,7 +60,8 @@ static void default_command_line() { command_line.should_keep_finished = 1; command_line.gzip = 0; command_line.send_output_by_mail = 0; - command_line.label = 0; + command_line.linux_cmd = NULL; + command_line.label = NULL; command_line.depend_on_size = 0; command_line.depend_on = NULL; /* -1 means depend on previous */ command_line.max_slots = 1; @@ -71,6 +72,7 @@ static void default_command_line() { command_line.logfile = NULL; command_line.taskpid = 0; command_line.start_time = 0; + command_line.jobid = 0; } struct Msg default_msg() { @@ -137,8 +139,8 @@ static struct option longOptions[] = { {"unsetenv", required_argument, NULL, 0}, {"stop", optional_argument, NULL, 0}, {"cont", optional_argument, NULL, 0}, - {"hold", required_argument, NULL, 0}, - {"restart", required_argument, NULL, 0}, + {"pause", required_argument, NULL, 0}, + {"rerun", required_argument, NULL, 0}, {"lock-ts", no_argument, NULL, 0}, {"unlock-ts", no_argument, NULL, 0}, {"daemon", no_argument, NULL, 0}, @@ -155,7 +157,7 @@ void parse_opts(int argc, char **argv) { /* Parse options */ while (1) { c = getopt_long(argc, argv, - ":AXRTVhKzClnfmBE:a:F:t:c:o:p:w:k:r:u:s:U:qi:N:L:dS:D:W:O:", + ":AXRTVhKzClnfmBE:a:F:t:c:o:p:w:k:r:u:s:U:qi:N:J:L:dS:D:W:O:", longOptions, &optionIdx); if (c == -1) @@ -178,11 +180,11 @@ void parse_opts(int argc, char **argv) { } else if (strcmp(longOptions[optionIdx].name, "get_label") == 0) { command_line.request = c_GET_LABEL; command_line.jobid = str2int(optarg); - } else if (strcmp(longOptions[optionIdx].name, "hold") == 0) { - command_line.request = c_HOLD_JOB; + } else if (strcmp(longOptions[optionIdx].name, "pause") == 0) { + command_line.request = c_PAUSE_JOB; command_line.jobid = str2int(optarg); - } else if (strcmp(longOptions[optionIdx].name, "restart") == 0) { - command_line.request = c_RESTART_JOB; + } else if (strcmp(longOptions[optionIdx].name, "rerun") == 0) { + command_line.request = c_RERUN_JOB; command_line.jobid = str2int(optarg); } else if (strcmp(longOptions[optionIdx].name, "lock-ts") == 0) { command_line.request = c_LOCK_SERVER; @@ -311,6 +313,11 @@ void parse_opts(int argc, char **argv) { if (command_line.num_slots < 0) command_line.num_slots = 0; break; + case 'J': + command_line.jobid = str2int(optarg); + if (command_line.jobid < 0) + command_line.jobid = 0; + break; /* case 'Z': command_line.taskpid = str2int(optarg); @@ -460,6 +467,8 @@ void parse_opts(int argc, char **argv) { get_command(optind, argc, argv); } + command_line.linux_cmd = charArray_string(argc, argv); + if (command_line.request != c_SHOW_HELP && command_line.request != c_SHOW_VERSION) command_line.need_server = 1; @@ -562,8 +571,8 @@ static void print_help(const char *cmd) { "texts.\n"); printf(" --daemon Run the server as daemon by Root " "only.\n"); - printf(" --hold [jobid] hold on a task.\n"); - printf(" --restart [jobid] rerun a hold task.\n"); + printf(" --pause [jobid] hold on a task.\n"); + printf(" --rerun [jobid] rerun a paused task.\n"); printf(" --lock Locker the server (Timeout: 30 " "sec.)" "For Root, timeout is infinity.\n"); @@ -700,14 +709,14 @@ int main(int argc, char **argv) { break; case c_CHECK_DAEMON: break; - case c_HOLD_JOB: - c_hold_job(command_line.jobid); + case c_PAUSE_JOB: + c_pause_job(command_line.jobid); c_wait_server_lines(); break; - case c_RESTART_JOB: - c_restart_job(command_line.jobid); - // c_wait_server_lines(); + case c_RERUN_JOB: + c_rerun_job(command_line.jobid); + c_wait_server_lines(); break; case c_LOCK_SERVER: errorlevel = c_lock_server(); diff --git a/main.h b/main.h index b13c3ab..6b3b79c 100644 --- a/main.h +++ b/main.h @@ -18,8 +18,8 @@ enum MsgTypes { LIST_ALL, LIST_LINE, REFRESH_USERS, - HOLD_JOB, - RESTART_JOB, + PAUSE_JOB, + RERUN_JOB, LOCK_SERVER, UNLOCK_SERVER, STOP_USER, @@ -72,8 +72,8 @@ enum Request { c_CONT_USER, c_LOCK_SERVER, c_UNLOCK_SERVER, - c_HOLD_JOB, - c_RESTART_JOB, + c_PAUSE_JOB, + c_RERUN_JOB, c_CLEAR_FINISHED, c_SHOW_HELP, c_SHOW_VERSION, @@ -122,6 +122,7 @@ struct CommandLine { char **array; int num; } command; + char *linux_cmd; char *label; char *logfile; char *outfile; @@ -141,7 +142,8 @@ extern int term_width; struct Msg; -enum Jobstate { QUEUED, RUNNING, FINISHED, SKIPPED, HOLDING_CLIENT, RELINK }; +enum Jobstate { QUEUED, RUNNING, FINISHED, SKIPPED, + HOLDING_CLIENT, RELINK, WAIT}; struct Msg { enum MsgTypes type; @@ -149,6 +151,8 @@ struct Msg { union { struct { int command_size; + int command_size_strip; + int path_size; int store_output; int should_keep_finished; int label_size; @@ -204,6 +208,8 @@ struct Job { struct Job *next; int jobid; char *command; + char *work_dir; + int command_strip; enum Jobstate state; struct Result result; /* Defined in msg.h */ char *output_filename; @@ -313,7 +319,7 @@ void s_list_all(int s); void s_list_plain(int s); -int s_newjob(int s, struct Msg *m, int ts_UID); +int s_newjob(int s, struct Msg *m, int ts_UID, int socket); void s_removejob(int jobid); @@ -515,15 +521,16 @@ const char *uid2user_name(int uid); int read_first_jobid_from_logfile(const char *path); void kill_pid(int ppid, const char *signal); char* linux_cmd(char* CMD, char* out, int out_size); +char **split_str(const char *str, int *size); void check_running_task(int pid); +char *charArray_string(int num, char** array); /* locker */ int user_locker; time_t locker_time; int jobsort_flag; -// FILE* dbf; // # DEBUG int check_ifsleep(int pid); -int check_running_dead(int jobid); +// int check_running_dead(int jobid); /* jobs.c */ void s_user_status_all(int s); @@ -534,22 +541,39 @@ void s_stop_all_users(int s); void s_stop_user(int s, int uid); void s_cont_user(int s, int uid); void s_cont_all_users(int s); -void s_hold_job(int s, int jobid, int uid); -void s_restart_job(int s, int jobid, int uid); +void s_pause_job(int s, int jobid, int uid); +void s_rerun_job(int s, int jobid, int uid); void s_lock_server(int s, int uid); void s_unlock_server(int s, int uid); int s_check_locker(int s, int uid); void s_set_jobids(int i); void s_sort_jobs(); int s_check_relink(int s, struct Msg *m, int ts_UID); - +void s_read_sqlite(); +int s_check_running_pid(int pid); /* client.c */ void c_list_jobs_all(); void c_stop_user(int uid); void c_cont_user(int uid); -void c_hold_job(int jobid); +void c_pause_job(int jobid); +void c_rerun_job(int jobid); int c_lock_server(); int c_unlock_server(); -void c_restart_job(int jobid); void c_check_daemon(); + +/* sqlite.c */ +const char *get_sqlite_path(); +int open_sqlite(); +void close_sqlite(); +int insert_DB(struct Job* job, const char* table); +int insert_or_replace_DB(struct Job* job, const char* table); +struct Job* read_DB(int jobid, const char* table); +int read_jobid_DB(int** jobids, const char* table); +int delete_DB(int jobid, const char* table); +void movetop_DB(int jobid); +void swap_DB(int, int); +void set_jobids_DB(int value); +int get_jobids_DB(); +int jobDB_num, jobDB_wait_num; +struct Job** jobDB_Jobs; diff --git a/server.c b/server.c index 5bb7335..150b5e6 100644 --- a/server.c +++ b/server.c @@ -7,6 +7,7 @@ #include #include #include +#include #ifdef linux #include @@ -169,16 +170,55 @@ static int get_max_descriptors() { return max; } +static void check_jobDB() { + struct Job* p; + for (int i = 0; i < jobDB_num; i++) { + p = jobDB_Jobs[i]; + if (p->pid != 0 && s_check_running_pid(p->pid) != 1) { + struct Result r = default_result(); + + r.errorlevel = -1; + r.died_by_signal = 1; + r.signal = SIGKILL; + r.user_ms = 0; + r.system_ms = 0; + r.real_ms = 0; + r.skipped = 0; + + // warning("JobID %i quit while running.", jobid); + job_finished(&r, p->jobid); + check_notify_list(p->jobid); + + jobDB_num--; + jobDB_Jobs[i] = jobDB_Jobs[jobDB_num]; + return; + } + } +} -void server_main(int notify_fd, char *_path) { - // dbf = fopen("/home/kylin/task-spooler/debug.txt", "w"); - // fprintf(dbf, "start server_main\n"); - // fflush(dbf); +/* +int s_add_connection(int jobid, int socket, int hasjob, int ts_UID) { + if (nconnections < MAXCONN) { + client_cs[nconnections].jobid = jobid; + client_cs[nconnections].socket = socket; + client_cs[nconnections].hasjob = hasjob; + client_cs[nconnections].ts_UID = ts_UID; + nconnections++; + return 0; + } else { + return 1; + } +} +*/ + +void server_main(int notify_fd, char *_path) { int ls; struct sockaddr_un addr; int res; char *dirpath; + signal(SIGCLD, SIG_IGN); + signal(SIGCHLD, SIG_IGN); process_type = SERVER; max_descriptors = get_max_descriptors(); @@ -221,15 +261,14 @@ void server_main(int notify_fd, char *_path) { user_jobs[i] = 0; user_queue[i] = 0; } + jobDB_num = jobDB_wait_num = 0; + jobDB_Jobs = NULL; set_server_logfile(); - int jobid = read_first_jobid_from_logfile(logfile_path); - s_set_jobids(get_env("TS_FIRST_JOBID", jobid)); - jobsort_flag = get_env("TS_SORTJOBS", 0); - + // int jobid = read_first_jobid_from_logfile(logfile_path); read_user_file(get_user_path()); set_socket_model(_path); - + install_sigterm_handler(); set_default_maxslots(); @@ -238,10 +277,22 @@ void server_main(int notify_fd, char *_path) { if (notify_fd != 0) notify_parent(notify_fd); + + if (open_sqlite() != 0) { + debug_write("Cannot open sqlite database"); + error("Cannot open sqlite database"); + } + + // printf("jobids = %d\n", get_jobids_DB()); + jobsort_flag = get_env("TS_SORTJOBS", 0); + s_set_jobids(get_env("TS_FIRST_JOBID", get_jobids_DB())); + s_read_sqlite(); printf("Start main server loops...\n"); server_loop(ls); } + + static int get_conn_of_jobid(int jobid) { int i; for (i = 0; i < nconnections; ++i) @@ -295,7 +346,7 @@ static void server_loop(int ls) { if (client_cs[nconnections].ts_UID == -1) { close(cs); } else { - ++nconnections; + nconnections++; } } @@ -320,7 +371,11 @@ static void server_loop(int ls) { */ } /* This will return firstjob->jobid or -1 */ + if (jobDB_num > 0) { + check_jobDB(); + } newjob = next_run_job(); + if (newjob != -1) { int conn, awaken_job; conn = get_conn_of_jobid(newjob); @@ -335,7 +390,7 @@ static void server_loop(int ls) { s_newjob_ok(wake_conn); } } - } + } // end of while (keep_loop) end_server(ls); } @@ -364,8 +419,6 @@ static void remove_connection(int index) { static void clean_after_client_disappeared(int socket, int index) { /* Act as if the job ended. */ int jobid = client_cs[index].jobid; - // fprintf(dbf, "clean %d from client_cs[%d]\n", jobid, index); - // fflush(dbf); if (client_cs[index].hasjob) { struct Result r = default_result(); @@ -432,13 +485,13 @@ static enum Break client_read(int index) { close(s); remove_connection(index); break; - case HOLD_JOB: - s_hold_job(s, m.jobid, ts_UID); + case PAUSE_JOB: + s_pause_job(s, m.jobid, ts_UID); close(s); remove_connection(index); break; - case RESTART_JOB: - s_restart_job(s, m.jobid, ts_UID); + case RERUN_JOB: + s_rerun_job(s, m.jobid, ts_UID); close(s); remove_connection(index); break; @@ -495,8 +548,14 @@ static enum Break client_read(int index) { break; } - client_cs[index].jobid = s_newjob(s, &m, ts_UID); + client_cs[index].jobid = s_newjob(s, &m, ts_UID, s); client_cs[index].hasjob = 1; + if (client_cs[index].jobid == -1) { + s_newjob_nok(index); + client_cs[index].hasjob = 0; + clean_after_client_disappeared(s, index); + break; + } if (!job_is_holding_client(client_cs[index].jobid)) s_newjob_ok(index); else if (!m.u.newjob.wait_enqueuing) { @@ -677,8 +736,6 @@ static void s_runjob(int jobid, int index) { error("Run job of the client %i which doesn't have any job", index); s = client_cs[index].socket; - // fprintf(dbf, "socket = %d, jobid = %d\n", s, jobid); - // fflush(dbf); s_send_runjob(s, jobid); } diff --git a/server_start.c b/server_start.c index 739a75c..c839636 100644 --- a/server_start.c +++ b/server_start.c @@ -200,6 +200,8 @@ static void server_info() { printf(" Socket path: %s [TS_SOCKET]\n", socket_path); printf(" Read user file from %s [TS_USER_PATH]\n", get_user_path()); printf(" Write log file to %s [TS_LOGFILE_PATH]\n", set_server_logfile()); + printf(" Sqlite Database @ %s [TS_SQLITE_PATH]\n", get_sqlite_path()); + setup_kill_sh(); } diff --git a/user.c b/user.c index ff6da4e..38719d4 100644 --- a/user.c +++ b/user.c @@ -7,12 +7,11 @@ #include #include #include +#include #include "main.h" #include "user.h" - -#define DEFAUL_USER_PATH "/home/kylin/task-spooler/user.txt" -#define DEFAUL_LOG_PATH "/home/kylin/task-spooler/log.txt" +#include "default_path.h" void send_list_line(int s, const char *str); void error(const char *str, ...); @@ -23,7 +22,7 @@ const char *get_user_path() { char *str; str = getenv("TS_USER_PATH"); if (str == NULL || strlen(str) == 0) { - return DEFAUL_USER_PATH; + return DEFAULT_USER_PATH; } else { return str; } @@ -43,6 +42,27 @@ int get_env(const char *env, int v0) { } } +//按空格自动分割子串的函数 +char **split_str(const char *str0, int *size) { + char **result = (char**)malloc(sizeof(char*)); //存储分割后的子串 + char *str = (char*) malloc(sizeof(char)*strlen(str0)); + strcpy(str, str0); + int n = 0; //数组的大小 + char *token; //分割得到的子串 + token = strtok(str, " "); //以空格为分隔符分割字符串 + while (token != NULL) { //循环分割,直到遇到NULL + result = realloc(result, (n + 1) * sizeof(char *)); //重新分配内存空间,增加一个元素 + if (result == NULL) { //如果内存分配失败,返回NULL + return NULL; + } + result[n] = token; //将子串存入数组 + n++; //更新数组的大小 + token = strtok(NULL, " "); //继续分割 + } + *size = n; //返回数组的大小 + return result; //返回数组 +} + char* linux_cmd(char* CMD, char* out, int out_size) { FILE *fp; /* Open the command for reading. */ @@ -76,7 +96,7 @@ long str2int(const char *str) { const char *set_server_logfile() { logfile_path = getenv("TS_LOGFILE_PATH"); if (logfile_path == NULL || strlen(logfile_path) == 0) { - logfile_path = DEFAUL_LOG_PATH; + logfile_path = DEFAULT_LOG_PATH; } return logfile_path; } From bfd82f54bcf38d02603a9568fbc65991f661b5b9 Mon Sep 17 00:00:00 2001 From: kylin Date: Sat, 25 Mar 2023 18:05:57 +0800 Subject: [PATCH 47/91] add the feature to IO the int array for notify --- execute.c | 3 ++- jobs.c | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/execute.c b/execute.c index f0cff27..1533a0f 100644 --- a/execute.c +++ b/execute.c @@ -5,7 +5,6 @@ Please find the license in the provided COPYING file. */ #include -#include #include #include #include @@ -20,6 +19,8 @@ #include #include +#include + #include "main.h" /* from signals.c */ diff --git a/jobs.c b/jobs.c index 6115aa9..9d139ba 100644 --- a/jobs.c +++ b/jobs.c @@ -1099,7 +1099,7 @@ static int fork_cmd(int UID, const char* path, const char* cmd) { } return pid; } -// TODO add the check of running state. + static void s_add_job(struct Job* j, struct Job** p) { if (j->state == RUNNING || j->state == HOLDING_CLIENT || j->state == RELINK) { if (j->pid > 0 && s_check_running_pid(j->pid) == 1) { @@ -1108,7 +1108,7 @@ static void s_add_job(struct Job* j, struct Job** p) { int ts_UID = j->ts_UID; user_jobs[ts_UID]++; - if (j->state == RUNNING) { + if (j->state == RUNNING && check_ifsleep(j->pid) == 1) { int slots = j->num_slots; user_busy[ts_UID] += slots; busy_slots += slots; From 1b8645d2baefc667fcea34c59245c1acdb220fda Mon Sep 17 00:00:00 2001 From: kylin Date: Sat, 25 Mar 2023 19:32:45 +0800 Subject: [PATCH 48/91] zero to job->results floats and the fix the bug on adding the jobs --- jobs.c | 8 ++++++-- server_start.c | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/jobs.c b/jobs.c index 9d139ba..8192290 100644 --- a/jobs.c +++ b/jobs.c @@ -559,7 +559,11 @@ static struct Job *newjobptr() { info->enqueue_time.tv_usec = 0; info->start_time.tv_usec = 0; info->end_time.tv_usec = 0; - + struct Result* result = &(p->next->result); + result->user_ms = 0.0; + result->system_ms = 0.0; + result->real_ms = 0.0; + return p->next; } @@ -1108,7 +1112,7 @@ static void s_add_job(struct Job* j, struct Job** p) { int ts_UID = j->ts_UID; user_jobs[ts_UID]++; - if (j->state == RUNNING && check_ifsleep(j->pid) == 1) { + if (j->state == RUNNING && check_ifsleep(j->pid) == 0) { int slots = j->num_slots; user_busy[ts_UID] += slots; busy_slots += slots; diff --git a/server_start.c b/server_start.c index c839636..347e81f 100644 --- a/server_start.c +++ b/server_start.c @@ -256,7 +256,7 @@ int ensure_server_up(int daemonFlag) { error("getting the server socket"); create_socket_path(&socket_path); - + if (daemonFlag == 1) remove(socket_path); // try to delete it res = try_connect(server_socket); /* Good connection */ From f265ff41840ee75ddebdcd9efd9aa29ac928c704 Mon Sep 17 00:00:00 2001 From: kylin Date: Sat, 25 Mar 2023 20:14:14 +0800 Subject: [PATCH 49/91] update the kill_pid --- client.c | 4 ++-- jobs.c | 13 ++----------- list.c | 6 +++--- server_start.c | 2 +- user.c | 2 +- 5 files changed, 9 insertions(+), 18 deletions(-) diff --git a/client.c b/client.c index 3cf3c34..a7cc1ab 100644 --- a/client.c +++ b/client.c @@ -495,7 +495,7 @@ void c_pause_job(int jobid) { // printf("kill the pid: %d\n", pid); /* Send SIGTERM to the process group, as pid is for process group */ // kill(-pid, SIGSTOP); - kill_pid(pid, "STOP"); + kill_pid(pid, "kill -s STOP"); struct Msg m = default_msg(); m.type = PAUSE_JOB; @@ -523,7 +523,7 @@ void c_rerun_job(int jobid) { send_msg(server_socket, &m); // not error, restart job if (wait_server_lines_and_check("Error") == 0) { - kill_pid(pid, "CONT"); + kill_pid(pid, "kill -s CONT"); } } diff --git a/jobs.c b/jobs.c index 8192290..dab204b 100644 --- a/jobs.c +++ b/jobs.c @@ -1372,8 +1372,7 @@ void s_cont_user(int s, int ts_UID) { if (p->ts_UID == ts_UID && p->state == RUNNING) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { - // kill(p->pid, SIGCONT); - kill_pid(p->pid, "CONT"); + kill_pid(p->pid, "kill -s CONT"); } } p = p->next; @@ -1396,8 +1395,7 @@ void s_stop_user(int s, int ts_UID) { if (p->ts_UID == ts_UID && p->state == RUNNING) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { - // kill(p->pid, SIGSTOP); - kill_pid(p->pid, "STOP"); + kill_pid(p->pid, "kill -s STOP"); } else { char *label = "(...)"; if (p->label != NULL) @@ -1567,9 +1565,6 @@ int s_remove_job(int s, int *jobid, int client_tsUID) { if (p->state == RUNNING) { if (p->pid != 0 && (p->ts_UID == client_tsUID)) { - // kill((p->pid), SIGTERM); - // kill_pid(p->pid, "-9"); - if (*jobid == -1) snprintf(buff, 255, "Running job of last job is removed.\n"); else @@ -1743,8 +1738,6 @@ void s_pause_job(int s, int jobid, int ts_UID) { } int job_tsUID = p->ts_UID; if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { - // kill(p->pid, SIGSTOP); - // kill_pid(p->pid, "-stop"); user_busy[ts_UID] -= p->num_slots; busy_slots -= p->num_slots; user_queue[ts_UID]--; @@ -1775,8 +1768,6 @@ void s_rerun_job(int s, int jobid, int ts_UID) { } int job_tsUID = p->ts_UID; if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { - // kill(p->pid, SIGCONT); - // kill_pid(p->pid, "-cont"); int num_slots = p->num_slots; if (user_busy[ts_UID] + num_slots < user_max_slots[ts_UID] && busy_slots + num_slots <= max_slots) { user_busy[ts_UID] += num_slots; diff --git a/list.c b/list.c index ce27f39..f887538 100644 --- a/list.c +++ b/list.c @@ -412,15 +412,15 @@ char *joblistdump_torun(const struct Job *p) { char *time_rep(float *t) { float time_in_sec = *t; char *unit = "s"; - if (time_in_sec > 60) { + if (time_in_sec > 250) { time_in_sec /= 60; unit = "m"; - if (time_in_sec > 60) { + if (time_in_sec > 100) { time_in_sec /= 60; unit = "h"; - if (time_in_sec > 24) { + if (time_in_sec > 50) { time_in_sec /= 24; unit = "d"; } diff --git a/server_start.c b/server_start.c index 347e81f..667b6a3 100644 --- a/server_start.c +++ b/server_start.c @@ -91,7 +91,7 @@ do then echo "${extra} ${pid}" else - ${extra} kill -s $2 ${pid} + ${extra} $2 ${pid} fi done diff --git a/user.c b/user.c index 38719d4..ba57bdb 100644 --- a/user.c +++ b/user.c @@ -299,7 +299,7 @@ void kill_pid(int ppid, const char *signal) { FILE *fp; char command[1024]; char *path = get_kill_sh_path(); - sprintf(command, "bash %s %d %s", path, ppid, signal); + sprintf(command, "bash %s %d \"%s\"", path, ppid, signal); // printf("command = %s\n", command); fp = popen(command, "r"); free(path); From 819b063d95efc3ab7b9b297f88a5f5ccc3135d2c Mon Sep 17 00:00:00 2001 From: kylin Date: Sat, 25 Mar 2023 22:28:20 +0800 Subject: [PATCH 50/91] add sqlite.c --- sqlite.c | 480 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 480 insertions(+) create mode 100644 sqlite.c diff --git a/sqlite.c b/sqlite.c new file mode 100644 index 0000000..7b6f70f --- /dev/null +++ b/sqlite.c @@ -0,0 +1,480 @@ +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "default_path.h" + +sqlite3 *db = NULL; + +const char *get_sqlite_path() { + char *str; + str = getenv("TS_SQLITE_PATH"); + if (str == NULL || strlen(str) == 0) { + return DEFAULT_SQLITE_PATH; + } else { + return str; + } +} + +static char* int_array_to_string(int size, int* array) { + char *result = malloc(size * 12 + 1); + result[0] = '\0'; + for (int i = 0; i < size; i++) { + char buffer[12]; + sprintf(buffer, "%d", array[i]); + strcat(result, buffer); + if (i < size - 1) { + strcat(result, ","); + } + } + return result; +} + +static int* string_to_intArray(int *size, char* str) { + int count = 0; + for (int i = 0; str[i]; i++) { + if (str[i] == ',') { + count++; + } + } + count++; + int *result = malloc(count * sizeof(int)); + char *token = strtok(str, ","); + int index = 0; + while (token != NULL) { + result[index++] = atoi(token); + token = strtok(NULL, ","); + } + *size = count; + return result; +} + +static int callback(void *max, int argc, char **argv, char **azColName) { + if (argv[0]) { + *(int*)max = atoi(argv[0]); + } else { + *(int*)max = 0; + } + return 0; +} + +static int check_order_id(const char* op) { + char sql[100]; + char *err_msg = NULL; + sprintf(sql, "SELECT %s(order_id) FROM Jobs", op); + int value = 0; + int rc = sqlite3_exec(db, sql, callback, &value, &err_msg); + if (rc != SQLITE_OK ) { + fprintf(stderr, "[check_order_id] SQL error: %s, sql: %s\n", + sql, err_msg); + sqlite3_free(err_msg); + } + return value; +} + +static int max_order_id() { + return check_order_id("MAX"); +} + +static int min_order_id() { + return check_order_id("MIN"); +} + +static int get_order_id(int jobid) { + char *err_msg = 0; + char sql[1024]; + int value = 0; + sprintf(sql, "SELECT order_id FROM Jobs WHERE jobid=%d", jobid); + int rc = sqlite3_exec(db, sql, callback, &value, &err_msg); + if (rc != SQLITE_OK ) { + fprintf(stderr, "[get_order_id] SQL error: %s\n", err_msg); + sqlite3_free(err_msg); + return 0; + } + return value; +} + +void close_sqlite() { + free(jobDB_Jobs); + sqlite3_close(db); +} + + +int open_sqlite() { + const char* path = get_sqlite_path(); + char *zErrMsg = 0; + int rc; + rc = sqlite3_open(path, &db); + + if (rc) { + printf("Can't open database: %s\n", sqlite3_errmsg(db)); + sqlite3_close(db); + return(1); + } + + char *sql = "CREATE TABLE IF NOT EXISTS Jobs(" \ + "jobid INT PRIMARY KEY NOT NULL," \ + "command TEXT NOT NULL," \ + "state INT NOT NULL," \ + "output_filename TEXT NOT NULL," \ + "store_output INT NOT NULL," \ + "pid INT NOT NULL," \ + "ts_UID INT NOT NULL," \ + "should_keep_finished INT NOT NULL," \ + "depend_on INT NOT NULL," \ + "depend_on_size INT NOT NULL," \ + "notify_errorlevel_to INT NOT NULL," \ + "notify_errorlevel_to_size INT NOT NULL," \ + "dependency_errorlevel INT NOT NULL," \ + "label TEXT NOT NULL," \ + "num_slots INT NOT NULL, " \ + "errorlevel INT NOT NULL, died_by_signal INT NOT NULL, signal INT NOT NULL, "\ + "user_ms FLOAT NOT NULL, system_ms FLOAT NOT NULL, real_ms FLOAT NOT NULL, skipped INT NOT NULL, " \ + "ptr TEXT NOT NULL, nchars INT NOT NULL, allocchars INT NOT NULL, "\ + "enqueue_time INT NOT NULL, start_time INT NOT NULL, end_time INT NOT NULL, " \ + "enqueue_time_ms INT NOT NULL, start_time_ms INT NOT NULL, end_time_ms INT NOT NULL, " \ + "order_id INT NOT NULL, command_strip INT NOT NULL, work_dir TEXT NOT NULL);"; + + rc = sqlite3_exec(db, sql, 0, 0, &zErrMsg); + + if (rc != SQLITE_OK) { + printf("[open_sqlite0] SQL error: %s\n", zErrMsg); + sqlite3_free(zErrMsg); + } else { + printf("Table Jobs created successfully\n"); + } + + char *sql2 = "CREATE TABLE IF NOT EXISTS Finished(" \ + "jobid INT PRIMARY KEY NOT NULL," \ + "command TEXT NOT NULL," \ + "state INT NOT NULL," \ + "output_filename TEXT NOT NULL," \ + "store_output INT NOT NULL," \ + "pid INT NOT NULL," \ + "ts_UID INT NOT NULL," \ + "should_keep_finished INT NOT NULL," \ + "depend_on INT NOT NULL," \ + "depend_on_size INT NOT NULL," \ + "notify_errorlevel_to INT NOT NULL," \ + "notify_errorlevel_to_size INT NOT NULL," \ + "dependency_errorlevel INT NOT NULL," \ + "label TEXT NOT NULL," \ + "num_slots INT NOT NULL, " \ + "errorlevel INT NOT NULL, died_by_signal INT NOT NULL, signal INT NOT NULL, "\ + "user_ms FLOAT NOT NULL, system_ms FLOAT NOT NULL, real_ms FLOAT NOT NULL, skipped INT NOT NULL, " \ + "ptr TEXT NOT NULL, nchars INT NOT NULL, allocchars INT NOT NULL, "\ + "enqueue_time INT NOT NULL, start_time INT NOT NULL, end_time INT NOT NULL, " \ + "enqueue_time_ms INT NOT NULL, start_time_ms INT NOT NULL, end_time_ms INT NOT NULL, " \ + "order_id INT NOT NULL, command_strip INT NOT NULL, work_dir TEXT NOT NULL);"; + + rc = sqlite3_exec(db, sql2, 0, 0, &zErrMsg); + + if (rc != SQLITE_OK) { + printf("[open_sqlite1] SQL error: %s\n", zErrMsg); + sqlite3_free(zErrMsg); + } else { + printf("Table Finished created successfully\n"); + } + + sql = "CREATE TABLE IF NOT EXISTS Global(" \ + "id INT PRIMARY KEY NOT NULL," \ + "JOBIDs INT NOT NULL); INSERT INTO Global (id, JOBIDs) VALUES (1, 1000);"; + rc = sqlite3_exec(db, sql, 0, 0, &zErrMsg); + if (rc != SQLITE_OK ) { + printf("[open_sqlite2] SQL error: %s\n", zErrMsg); + sqlite3_free(zErrMsg); + } + + return 0; +} + +int get_jobids_DB() { + char *err_msg = 0; + char *sql = "SELECT JOBIDs FROM Global WHERE id=1;"; + int value = 0; + int rc = sqlite3_exec(db, sql, callback, &value, &err_msg); + if (rc != SQLITE_OK ) { + fprintf(stderr, "[get_jobids_DB] SQL error: %s\n", err_msg); + sqlite3_free(err_msg); + return 1000; + } + return value; +} + +void set_jobids_DB(int value) { + char *err_msg = 0; + char sql[1024]; + sprintf(sql, "INSERT OR REPLACE INTO Global (id, JOBIDs) VALUES (1, %d);", value); + int rc = sqlite3_exec(db, sql, 0, 0, &err_msg); + if (rc != SQLITE_OK ) { + fprintf(stderr, "[set_jobids_DB] SQL error: %s\n", err_msg); + sqlite3_free(err_msg); + } +} + +int delete_DB(int jobid , const char* table) { + char sql[1024]; + sprintf(sql,"DELETE FROM %s WHERE jobid=%d;", table, jobid); + char* errmsg=NULL; + + if(sqlite3_exec(db ,sql,NULL,NULL,&errmsg)!=SQLITE_OK){ + fprintf(stderr,"[delete_DB] SQL error: %s by %s\n",errmsg, sql); + sqlite3_free(errmsg); + return -1;//返回-1表示删除失败 + } + return 0;//返回0表示删除成功 +} + + +static int edit_DB(struct Job* job, const char* table, const char* action) { + struct Result* result = &(job->result); + struct Procinfo* info= &(job->info); + const char* label = job->label == NULL ? "(..)" : job->label; + char sql[1024]; + + int order_id = get_order_id(job->jobid); + if (order_id == 0) { + order_id = max_order_id() + 1; + } + char* depend_on = int_array_to_string(job->depend_on_size, job->depend_on); + char* notify_errorlevel_to = int_array_to_string(job->notify_errorlevel_to_size, job->notify_errorlevel_to); + + sprintf(sql, "%s INTO %s (jobid, command, state, output_filename, store_output, pid, ts_UID, should_keep_finished, depend_on, depend_on_size," \ + "notify_errorlevel_to, notify_errorlevel_to_size, dependency_errorlevel,label,num_slots,errorlevel,died_by_signal," \ + "signal,user_ms,system_ms,real_ms,skipped," \ + "ptr,nchars,allocchars," \ + "enqueue_time,start_time,end_time," \ + "enqueue_time_ms,start_time_ms,end_time_ms, " \ + "order_id, command_strip, work_dir)" \ + "VALUES (%d,'%s',%d,'%s',%d,%d,%d,%d,'%s',%d,'%s',%d,%d,'%s',%d," \ + "%d,%d,%d,%f,%f,%f,%d,"\ + "'%s',%d,%d,'%ld','%ld','%ld','%ld','%ld','%ld', " \ + "%d, %d,'%s');", + action, + table, + job->jobid, + job->command, + job->state, + job->output_filename, + job->store_output, + job->pid, + job->ts_UID, + job->should_keep_finished, + depend_on, // job->depend_on, + job->depend_on_size, + notify_errorlevel_to, + job->notify_errorlevel_to_size, + job->dependency_errorlevel, + label, + job->num_slots, + result->errorlevel, result->died_by_signal, result->signal, + result->user_ms, result->system_ms, result->real_ms, result->skipped, + info->ptr, info->nchars, info->allocchars, + info->enqueue_time.tv_sec, info->start_time.tv_sec, info->end_time.tv_sec, + info->enqueue_time.tv_usec, info->start_time.tv_usec, info->end_time.tv_usec, + order_id, job->command_strip, job->work_dir + ); + char *errmsg = NULL; + int rs = sqlite3_exec(db, sql,NULL,NULL,&errmsg); + free(depend_on); + free(notify_errorlevel_to); + if (rs != SQLITE_OK) { + fprintf(stderr,"[insert_DB] SQL error: %s by %s\n", errmsg, sql); + sqlite3_free(errmsg); + return -1; // 返回-1表示插入失败 + } + return 0; // 返回0表示插入成功 +} + + +int insert_DB(struct Job* job, const char* table) { + return edit_DB(job, table, "INSERT"); +} + +int insert_or_replace_DB(struct Job* job, const char* table) { + return edit_DB(job, table, "INSERT OR REPLACE"); +} + +static void set_order_id_DB(int jobid, int order_id) { + char *err_msg = 0; + char sql[1024]; + sprintf(sql, "UPDATE Jobs SET order_id=%d WHERE jobid=%d", order_id, jobid); + int rc = sqlite3_exec(db, sql, 0, 0, &err_msg); + if (rc != SQLITE_OK ) { + fprintf(stderr, "[movetop_DB] SQL error: %s\n", err_msg); + sqlite3_free(err_msg); + } +} + +void swap_DB(int jobid0, int jobid1) { + int id0 = get_order_id(jobid0); + int id1 = get_order_id(jobid1); + set_order_id_DB(jobid0, id1); + set_order_id_DB(jobid1, id0); +} + +void movetop_DB(int jobid) { + int order_id = min_order_id() - 1; + if (order_id == 0) order_id = -1; + set_order_id_DB(jobid, order_id); +} + +/* +static void clear_DB(const char* table) { + char sql[1024]; + char* err_msg; + sprintf(sql, "DELETE FROM %s", table); + int rc = sqlite3_exec(db, sql, 0, 0, &err_msg); + if (rc != SQLITE_OK ) { + fprintf(stderr, "[clear_DB] SQL error: %s\n", err_msg); + sqlite3_free(err_msg); + } +} +*/ +int read_jobid_DB(int** jobids, const char* table) { + int n; + char sql[1024]; + sprintf(sql, "SELECT COUNT(*) FROM %s;", table); + char *errmsg = NULL; + + if (sqlite3_exec(db, sql,NULL,NULL,&errmsg) != SQLITE_OK) { + fprintf(stderr,"[read_jobid_DB0] SQL error: %s by %s\n", + sqlite3_errmsg(db), sql); + sqlite3_free(errmsg); + return -1; // 返回-1表示查询失败 + } + + // 从查询结果中读取数据 + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + fprintf(stderr,"[read_jobid_DB1] SQL error: %s by %s\n", + sqlite3_errmsg(db), sql); + return -2; // 返回-1表示查询失败 + } + + if (sqlite3_step(stmt) == SQLITE_ROW) { + n = sqlite3_column_int(stmt, 0); + } + if (n == 0) return 0; + *jobids = (int*)malloc(n * sizeof(int)); + + sprintf(sql, "SELECT jobid FROM %s ORDER BY order_id;", table); + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + fprintf(stderr,"[read_jobid_DB2] SQL error: %s from %s\n", + sqlite3_errmsg(db), sql); + debug_write("test0"); + return -3; // 返回-1表示查询失败 + } + + int i = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) { + (*jobids)[i++] = sqlite3_column_int(stmt, 0); + } + + return n; // 返回0表示查询成功 +} + + +struct Job* read_DB(int jobid, const char* table) { + struct Job* job = (struct Job*)malloc(sizeof(struct Job)*1); + struct Result* result = &(job->result); + struct Procinfo* info= &(job->info); + + char sql[2048]; + sprintf(sql, "SELECT * FROM %s WHERE jobid=%d;", table, jobid); + char *errmsg = NULL; + + if (sqlite3_exec(db, sql,NULL,NULL,&errmsg) != SQLITE_OK) { + fprintf(stderr,"[read_DB0] SQL error: %s\n", errmsg); + sqlite3_free(errmsg); + return NULL; // 返回-1表示查询失败 + } + + // 从查询结果中读取数据 + sqlite3_stmt *stmt; + int size, rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + fprintf(stderr,"[read_DB1] SQL error: %s\n", sqlite3_errmsg(db)); + return NULL; // 返回-1表示查询失败 + } + + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + // 从查询结果中读取数据 + job->jobid = sqlite3_column_int(stmt, 0); + + strcpy(sql, (const char*) sqlite3_column_text(stmt, 1)); + job->command = (char*) malloc(sizeof(char) * (strlen(sql)+1)); + strcpy(job->command, sql); + + job->state = sqlite3_column_int(stmt, 2); + + strcpy(sql, (const char*) sqlite3_column_text(stmt, 3)); + job->output_filename = (char*) malloc(sizeof(char) * (strlen(sql)+1)); + strcpy(job->output_filename, sql); + + job->store_output = sqlite3_column_int(stmt, 4); + job->pid = sqlite3_column_int(stmt, 5); + job->ts_UID = sqlite3_column_int(stmt, 6); + job->should_keep_finished = sqlite3_column_int(stmt, 7); + + + // job->depend_on_size = sqlite3_column_bytes(stmt, 9); + // job->notify_errorlevel_to_size = sqlite3_column_bytes(stmt, 11); + strcpy(sql, (const char*) sqlite3_column_text(stmt, 8)); + job->depend_on = string_to_intArray(&size, sql); + job->depend_on_size = size; + strcpy(sql, (const char*) sqlite3_column_text(stmt, 8)); + job->notify_errorlevel_to = string_to_intArray(&size, sql); + job->notify_errorlevel_to_size = size; + + + job->dependency_errorlevel = sqlite3_column_int(stmt, 12); + + strcpy(sql, (const char*) sqlite3_column_text(stmt, 13)); + job->label = (char*) malloc(sizeof(char) * (strlen(sql)+1)); + strcpy(job->label, sql); + + job->num_slots = sqlite3_column_int(stmt, 14); + + result->errorlevel = sqlite3_column_int(stmt, 15); + result->died_by_signal = sqlite3_column_int(stmt, 16); + result->signal = sqlite3_column_int(stmt, 17); + result->user_ms = (float)sqlite3_column_double(stmt, 18); + result->system_ms = (float)sqlite3_column_double(stmt, 19); + result->real_ms = (float)sqlite3_column_double(stmt, 20); + result->skipped = sqlite3_column_int(stmt, 21); + + strcpy(sql, (const char*) sqlite3_column_text(stmt, 22)); + info->ptr = (char*) malloc(sizeof(char) * (strlen(sql)+1)); + strcpy(info->ptr, sql); + + info->nchars=sqlite3_column_bytes(stmt,23)/sizeof(char); + info->allocchars=sqlite3_column_bytes(stmt,24)/sizeof(char); + + info->enqueue_time.tv_sec=sqlite3_column_int64(stmt,25); + info->start_time.tv_sec=sqlite3_column_int64(stmt,26); + info->end_time.tv_sec=sqlite3_column_int64(stmt,27); + + info->enqueue_time.tv_usec=sqlite3_column_int64(stmt,28); + info->start_time.tv_usec=sqlite3_column_int64(stmt,29); + info->end_time.tv_usec=sqlite3_column_int64(stmt,30); + job->command_strip=sqlite3_column_int(stmt, 32); + + strcpy(sql, (const char*) sqlite3_column_text(stmt, 33)); + job->work_dir = (char*) malloc(sizeof(char) * (strlen(sql)+1)); + strcpy(job->work_dir, sql); + + } else { + fprintf(stderr,"[read_DB2] SQL error: %s\n", sqlite3_errmsg(db)); + return NULL; // 返回-1表示查询失败 + } + + return job; // 返回0表示查询成功 +} From 4b89b6755e0a7b35a29be06d84a14ae9c206995f Mon Sep 17 00:00:00 2001 From: kylin Date: Sun, 26 Mar 2023 00:49:13 +0800 Subject: [PATCH 51/91] add taskset for cpu --- Makefile | 6 ++++-- client.c | 3 ++- jobs.c | 17 +++++++++++++---- kill_ppid.sh | 4 ++-- list.c | 4 +++- main.c | 2 +- main.h | 19 +++++++++++++++++-- print.c | 34 ++++++++++++++++++++++++++++++++++ server.c | 4 ++-- server_start.c | 2 +- sqlite.c | 42 ++++-------------------------------------- user.c | 5 ++--- 12 files changed, 85 insertions(+), 57 deletions(-) diff --git a/Makefile b/Makefile index abeeb1e..b161c48 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,8 @@ OBJECTS=main.o \ env.o \ tail.o \ user.o \ - sqlite.o + sqlite.o \ + taskset.o TARGET=ts INSTALL=install -c @@ -54,9 +55,10 @@ signals.o: signals.c main.h list.o: list.c main.h tail.o: tail.c main.h sqlite.o: sqlite.c main.h +taskset.o: taskset.c main.h clean: - rm -f *.o $(TARGET); killall ts; + rm -f *.o $(TARGET); killall ts; rm ts; install: $(TARGET) $(INSTALL) -d $(PREFIX)/bin diff --git a/client.c b/client.c index a7cc1ab..fb78840 100644 --- a/client.c +++ b/client.c @@ -495,12 +495,13 @@ void c_pause_job(int jobid) { // printf("kill the pid: %d\n", pid); /* Send SIGTERM to the process group, as pid is for process group */ // kill(-pid, SIGSTOP); - kill_pid(pid, "kill -s STOP"); struct Msg m = default_msg(); m.type = PAUSE_JOB; m.jobid = jobid; send_msg(server_socket, &m); + c_wait_server_lines(); + kill_pid(pid, "kill -s STOP"); } void c_rerun_job(int jobid) { diff --git a/jobs.c b/jobs.c index dab204b..675a618 100644 --- a/jobs.c +++ b/jobs.c @@ -809,13 +809,15 @@ int s_newjob(int s, struct Msg *m, int ts_UID, int socket) { p->info.start_time.tv_usec = 0; } - if(waitjob_flag == 0) insert_DB(p, "Jobs"); + if(waitjob_flag == 0) { + insert_DB(p, "Jobs"); + } set_jobids_DB(jobids); return p->jobid; } /* This assumes the jobid exists */ -void s_removejob(int jobid) { +void s_delete_job(int jobid) { struct Job *p; struct Job *newnext; /* @@ -952,6 +954,7 @@ static void new_finished_job(struct Job *j) { delete_DB(j->jobid, "Jobs"); insert_DB(j, "Finished"); + unlock_core_by_job(j); } static int job_is_in_state(int jobid, enum Jobstate state) { @@ -1116,6 +1119,7 @@ static void s_add_job(struct Job* j, struct Job** p) { int slots = j->num_slots; user_busy[ts_UID] += slots; busy_slots += slots; + set_task_cores(j); } jobDB_Jobs[jobDB_num] = j; @@ -1235,7 +1239,9 @@ void s_process_runjob_ok(int jobid, char *oname, int pid) { p->pid = pid; p->output_filename = oname; pinfo_set_start_time(&p->info); + insert_or_replace_DB(p, "Jobs"); + set_task_cores(p); write_logfile(p); } @@ -1373,6 +1379,7 @@ void s_cont_user(int s, int ts_UID) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { kill_pid(p->pid, "kill -s CONT"); + set_task_cores(p); } } p = p->next; @@ -1396,6 +1403,7 @@ void s_stop_user(int s, int ts_UID) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { kill_pid(p->pid, "kill -s STOP"); + unlock_core_by_job(p); } else { char *label = "(...)"; if (p->label != NULL) @@ -1731,6 +1739,7 @@ void s_pause_job(int s, int jobid, int ts_UID) { send_list_line(s, buff); return; } + if (check_ifsleep(p->pid) == 1) { snprintf(buff, 255, "job [%d] is aleady in PAUSE.\n", jobid); send_list_line(s, buff); @@ -1742,13 +1751,12 @@ void s_pause_job(int s, int jobid, int ts_UID) { busy_slots -= p->num_slots; user_queue[ts_UID]--; user_jobs[ts_UID]--; + unlock_core_by_job(p); snprintf(buff, 255, "To pause job [%d] successfully!\n", jobid); - } else { snprintf(buff, 255, "Error: cannot pause job [%d]\n", jobid); } send_list_line(s, buff); - } void s_rerun_job(int s, int jobid, int ts_UID) { @@ -1774,6 +1782,7 @@ void s_rerun_job(int s, int jobid, int ts_UID) { busy_slots += num_slots; user_queue[ts_UID]++; user_jobs[ts_UID]++; + set_task_cores(p); snprintf(buff, 255, "To rerun job [%d] successfully!\n", jobid); } else { snprintf(buff, 255, "Error: not enough slots [%d]\n", jobid); diff --git a/kill_ppid.sh b/kill_ppid.sh index 07accdf..d3c4904 100755 --- a/kill_ppid.sh +++ b/kill_ppid.sh @@ -41,10 +41,10 @@ fi for pid in ${pids}; do - if [ -z $2 ] + if [ -z "$2" ] then echo "${extra} ${pid}" else - ${extra} kill -s $2 ${pid} + ${extra} $2 ${pid} fi done diff --git a/list.c b/list.c index f887538..7118257 100644 --- a/list.c +++ b/list.c @@ -18,6 +18,7 @@ extern int busy_slots; extern int max_slots; +/* return 0 for run and 1 for running and -1 for error */ int check_ifsleep(int pid) { char filename[256]; char name[256]; @@ -27,11 +28,12 @@ int check_ifsleep(int pid) { fp = fopen(filename, "r"); if (fp == NULL) { - // fprintf(stderr, "Error: Couldn't open [%s]\n", filename); + fprintf(stderr, "Error: Couldn't open [%s]\n", filename); return -1; } int token = fscanf(fp, "%d %s %c", &pid, name, &status); if (token < 3) { + fprintf(stderr, "Error: not enough (3) tokens\n"); return -1; } fclose(fp); diff --git a/main.c b/main.c index 15ecf16..ea015a0 100644 --- a/main.c +++ b/main.c @@ -711,7 +711,7 @@ int main(int argc, char **argv) { break; case c_PAUSE_JOB: c_pause_job(command_line.jobid); - c_wait_server_lines(); + // c_wait_server_lines(); break; case c_RERUN_JOB: diff --git a/main.h b/main.h index 6b3b79c..03ff0a6 100644 --- a/main.h +++ b/main.h @@ -4,8 +4,13 @@ Please find the license in the provided COPYING file. */ +#include +#include -enum { CMD_LEN = 500, PROTOCOL_VERSION = 730 }; +enum { + CMD_LEN = 500, + PROTOCOL_VERSION = 730 +}; enum MsgTypes { KILL_SERVER, @@ -321,7 +326,7 @@ void s_list_plain(int s); int s_newjob(int s, struct Msg *m, int ts_UID, int socket); -void s_removejob(int jobid); +void s_delete_job(int jobid); void job_finished(const struct Result *result, int jobid); @@ -577,3 +582,13 @@ void set_jobids_DB(int value); int get_jobids_DB(); int jobDB_num, jobDB_wait_num; struct Job** jobDB_Jobs; + +/* print.c */ +char* ints_to_chars(int n, int *array, const char *delim); +int* chars_to_ints(int *size, char* str, const char* delim); + +/* taskset.c */ +void init_taskset(); +int set_task_cores(struct Job* p); +void unlock_core_by_job(struct Job* p); + diff --git a/print.c b/print.c index 80bda3c..a15c19c 100644 --- a/print.c +++ b/print.c @@ -45,3 +45,37 @@ int fd_nprintf(int fd, int maxsize, const char *fmt, ...) { return size; } + + +char *ints_to_chars(int n, int *array, const char *delim) { + int size = n * 12 + n * strlen(delim) + 1; + char *tmp = (char*) malloc(size * sizeof(char)); + int j = 0; + for (int i = 0; i < n; i++) { + j += sprintf(tmp + j, "%d", array[i]); + if (i < n - 1) { + strcat(tmp, delim); + j += strlen(delim); + } + } + return tmp; +} + +int* chars_to_ints(int *size, char* str, const char* delim) { + int count = 0; + for (int i = 0; str[i]; i++) { + if (str[i] == delim[0]) { + count++; + } + } + count++; + int *result = malloc(count * sizeof(int)); + char *token = strtok(str, delim); + int index = 0; + while (token != NULL) { + result[index++] = atoi(token); + token = strtok(NULL, delim); + } + *size = count; + return result; +} diff --git a/server.c b/server.c index 150b5e6..04e9c9a 100644 --- a/server.c +++ b/server.c @@ -263,7 +263,7 @@ void server_main(int notify_fd, char *_path) { } jobDB_num = jobDB_wait_num = 0; jobDB_Jobs = NULL; - + init_taskset(); set_server_logfile(); // int jobid = read_first_jobid_from_logfile(logfile_path); read_user_file(get_user_path()); @@ -407,7 +407,7 @@ static void remove_connection(int index) { int i; if (client_cs[index].hasjob) { - s_removejob(client_cs[index].jobid); + s_delete_job(client_cs[index].jobid); } for (i = index; i < (nconnections - 1); ++i) { diff --git a/server_start.c b/server_start.c index 667b6a3..3dfa260 100644 --- a/server_start.c +++ b/server_start.c @@ -87,7 +87,7 @@ fi for pid in ${pids}; do - if [ -z $2 ] + if [ -z "$2" ] then echo "${extra} ${pid}" else diff --git a/sqlite.c b/sqlite.c index 7b6f70f..abbca0e 100644 --- a/sqlite.c +++ b/sqlite.c @@ -1,9 +1,7 @@ #include -#include #include #include #include -#include #include "main.h" #include "default_path.h" @@ -20,38 +18,6 @@ const char *get_sqlite_path() { } } -static char* int_array_to_string(int size, int* array) { - char *result = malloc(size * 12 + 1); - result[0] = '\0'; - for (int i = 0; i < size; i++) { - char buffer[12]; - sprintf(buffer, "%d", array[i]); - strcat(result, buffer); - if (i < size - 1) { - strcat(result, ","); - } - } - return result; -} - -static int* string_to_intArray(int *size, char* str) { - int count = 0; - for (int i = 0; str[i]; i++) { - if (str[i] == ',') { - count++; - } - } - count++; - int *result = malloc(count * sizeof(int)); - char *token = strtok(str, ","); - int index = 0; - while (token != NULL) { - result[index++] = atoi(token); - token = strtok(NULL, ","); - } - *size = count; - return result; -} static int callback(void *max, int argc, char **argv, char **azColName) { if (argv[0]) { @@ -240,8 +206,8 @@ static int edit_DB(struct Job* job, const char* table, const char* action) { if (order_id == 0) { order_id = max_order_id() + 1; } - char* depend_on = int_array_to_string(job->depend_on_size, job->depend_on); - char* notify_errorlevel_to = int_array_to_string(job->notify_errorlevel_to_size, job->notify_errorlevel_to); + char* depend_on = ints_to_chars(job->depend_on_size, job->depend_on, ","); + char* notify_errorlevel_to = ints_to_chars(job->notify_errorlevel_to_size, job->notify_errorlevel_to, ","); sprintf(sql, "%s INTO %s (jobid, command, state, output_filename, store_output, pid, ts_UID, should_keep_finished, depend_on, depend_on_size," \ "notify_errorlevel_to, notify_errorlevel_to_size, dependency_errorlevel,label,num_slots,errorlevel,died_by_signal," \ @@ -428,10 +394,10 @@ struct Job* read_DB(int jobid, const char* table) { // job->depend_on_size = sqlite3_column_bytes(stmt, 9); // job->notify_errorlevel_to_size = sqlite3_column_bytes(stmt, 11); strcpy(sql, (const char*) sqlite3_column_text(stmt, 8)); - job->depend_on = string_to_intArray(&size, sql); + job->depend_on = chars_to_ints(&size, sql, ","); job->depend_on_size = size; strcpy(sql, (const char*) sqlite3_column_text(stmt, 8)); - job->notify_errorlevel_to = string_to_intArray(&size, sql); + job->notify_errorlevel_to = chars_to_ints(&size, sql, ","); job->notify_errorlevel_to_size = size; diff --git a/user.c b/user.c index ba57bdb..3b259f2 100644 --- a/user.c +++ b/user.c @@ -1,7 +1,6 @@ #define _GNU_SOURCE #include -#include #include #include #include @@ -295,11 +294,11 @@ int get_tsUID(int uid) { return -1; } -void kill_pid(int ppid, const char *signal) { +void kill_pid(int pid, const char *signal) { FILE *fp; char command[1024]; char *path = get_kill_sh_path(); - sprintf(command, "bash %s %d \"%s\"", path, ppid, signal); + sprintf(command, "bash %s %d \"%s\"", path, pid, signal); // printf("command = %s\n", command); fp = popen(command, "r"); free(path); From c5ff34bbad714b12e9f260c5ed7920b3b78669f9 Mon Sep 17 00:00:00 2001 From: kylin Date: Sun, 26 Mar 2023 08:41:08 +0800 Subject: [PATCH 52/91] fixed bug for the task_queue --- client.c | 2 ++ jobs.c | 41 ++++++++++++++++++-------- list.c | 6 ++-- main.c | 1 - taskset.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 16 deletions(-) create mode 100644 taskset.c diff --git a/client.c b/client.c index fb78840..79d2591 100644 --- a/client.c +++ b/client.c @@ -524,8 +524,10 @@ void c_rerun_job(int jobid) { send_msg(server_socket, &m); // not error, restart job if (wait_server_lines_and_check("Error") == 0) { + c_wait_server_lines(); kill_pid(pid, "kill -s CONT"); } + } int c_tail() { diff --git a/jobs.c b/jobs.c index 675a618..fc3853b 100644 --- a/jobs.c +++ b/jobs.c @@ -67,6 +67,26 @@ static void destroy_job(struct Job *p) { } } +static void free_cores(struct Job* p) { + if (p == NULL) return; + int ts_UID = p->ts_UID; + user_busy[ts_UID] -= p->num_slots; + busy_slots -= p->num_slots; + // user_queue[ts_UID]--; + user_jobs[ts_UID]--; + unlock_core_by_job(p); +} + +static int allocate_cores(struct Job* p) { + if (p == NULL) return 0; + int ts_UID = p->ts_UID; + user_busy[ts_UID] += p->num_slots; + busy_slots += p->num_slots; + // user_queue[ts_UID]++; + user_jobs[ts_UID]++; + return set_task_cores(p); +} + void send_list_line(int s, const char *str) { struct Msg m = default_msg(); @@ -1378,8 +1398,10 @@ void s_cont_user(int s, int ts_UID) { if (p->ts_UID == ts_UID && p->state == RUNNING) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { + if (check_ifsleep(p->pid) == 1) { + allocate_cores(p); + } kill_pid(p->pid, "kill -s CONT"); - set_task_cores(p); } } p = p->next; @@ -1402,8 +1424,10 @@ void s_stop_user(int s, int ts_UID) { if (p->ts_UID == ts_UID && p->state == RUNNING) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { + if (check_ifsleep(p->pid) == 0) { + free_cores(p); + } kill_pid(p->pid, "kill -s STOP"); - unlock_core_by_job(p); } else { char *label = "(...)"; if (p->label != NULL) @@ -1747,11 +1771,7 @@ void s_pause_job(int s, int jobid, int ts_UID) { } int job_tsUID = p->ts_UID; if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { - user_busy[ts_UID] -= p->num_slots; - busy_slots -= p->num_slots; - user_queue[ts_UID]--; - user_jobs[ts_UID]--; - unlock_core_by_job(p); + free_cores(p); snprintf(buff, 255, "To pause job [%d] successfully!\n", jobid); } else { snprintf(buff, 255, "Error: cannot pause job [%d]\n", jobid); @@ -1774,15 +1794,12 @@ void s_rerun_job(int s, int jobid, int ts_UID) { send_list_line(s, buff); return; } + int job_tsUID = p->ts_UID; if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { int num_slots = p->num_slots; if (user_busy[ts_UID] + num_slots < user_max_slots[ts_UID] && busy_slots + num_slots <= max_slots) { - user_busy[ts_UID] += num_slots; - busy_slots += num_slots; - user_queue[ts_UID]++; - user_jobs[ts_UID]++; - set_task_cores(p); + allocate_cores(p); snprintf(buff, 255, "To rerun job [%d] successfully!\n", jobid); } else { snprintf(buff, 255, "Error: not enough slots [%d]\n", jobid); diff --git a/list.c b/list.c index 7118257..1fb436f 100644 --- a/list.c +++ b/list.c @@ -17,7 +17,7 @@ /* From jobs.c */ extern int busy_slots; extern int max_slots; - +extern int core_usage; /* return 0 for run and 1 for running and -1 for error */ int check_ifsleep(int pid) { char filename[256]; @@ -83,9 +83,9 @@ char *joblist_headers() { line = malloc(256); snprintf(line, 256, - "%-4s %-7s %-7s %-6s %-10s %6s %-20s %s [run=%i/%i %.2f%%] %s\n", + "%-4s %-7s %-7s %-6s %-10s %6s %-20s %s [run=%i/%i %d %.2f%%] %s\n", "ID", "State", "Proc.", "User", "Label", "Time", "Command", "Log", - busy_slots, max_slots, 100.0 * busy_slots / max_slots, extra); + busy_slots, max_slots, core_usage, 100.0 * busy_slots / max_slots, extra); return line; } diff --git a/main.c b/main.c index ea015a0..b05ffd8 100644 --- a/main.c +++ b/main.c @@ -716,7 +716,6 @@ int main(int argc, char **argv) { break; case c_RERUN_JOB: c_rerun_job(command_line.jobid); - c_wait_server_lines(); break; case c_LOCK_SERVER: errorlevel = c_lock_server(); diff --git a/taskset.c b/taskset.c new file mode 100644 index 0000000..551380f --- /dev/null +++ b/taskset.c @@ -0,0 +1,87 @@ +#include +#include +#include + +#include "main.h" +#define MAX_CORE_NUM 16 // 256 +#define MAX_CORE_NUM_HALF 8 // 128 +#define MAX_CORE_NUM_QUAD 4 // 64 + +static int core_id[MAX_CORE_NUM]; +// = {0,64,1,65,2,66,3,67,4,68,5,69,6,70,7,71,8,72,9,73,10,74,11,75,12,76,13,77,14,78,15,79,16,80,17,81,18,82,19,83,20,84,21,85,22,86,23,87,24,88,25,89,26,90,27,91,28,92,29,93,30,94,31,95,32,96,33,97,34,98,35,99,36,100,37,101,38,102,39,103,40,104,41,105,42,106,43,107,44,108,45,109,46,110,47,111,48,112,49,113,50,114,51,115,52,116,53,117,54,118,55,119,56,120,57,121,58,122,59,123,60,124,61,125,62,126,63,127,128,192,129,193,130,194,131,195,132,196,133,197,134,198,135,199,136,200,137,201,138,202,139,203,140,204,141,205,142,206,143,207,144,208,145,209,146,210,147,211,148,212,149,213,150,214,151,215,152,216,153,217,154,218,155,219,156,220,157,221,158,222,159,223,160,224,161,225,162,226,163,227,164,228,165,229,166,230,167,231,168,232,169,233,170,234,171,235,172,236,173,237,174,238,175,239,176,240,177,241,178,242,179,243,180,244,181,245,182,246,183,247,184,248,185,249,186,250,187,251,188,252,189,253,190,254,191,255}; +static struct Job* core_jobs[MAX_CORE_NUM] = { NULL }; +int task_cores_id[MAX_CORE_NUM] = {0}; +int task_array_id[MAX_CORE_NUM] = {0}; +int task_core_num, core_usage; + +void init_taskset() { + printf("CPU taskset()\n"); + task_core_num = 0; + core_usage = 0; + + for (int i = 0; i < MAX_CORE_NUM; i++) { + core_id[i] = i / 2 + ((i % 2) + (i >= MAX_CORE_NUM_HALF)) * MAX_CORE_NUM_QUAD; + printf("[%3d] => %3d\t", i, core_id[i]); + if ((i+1)%8 == 0) printf("\n"); + } +} + +int allocate_cores(int N) { + if (N > MAX_CORE_NUM - core_usage) return 0; + task_core_num = 0; + int i = 0; + while(task_core_num < N && i < MAX_CORE_NUM) { + if (core_jobs[i] == NULL) { + task_cores_id[task_core_num] = core_id[i]; + task_array_id[task_core_num] = i; + task_core_num++; + } + i++; + } + if (task_core_num != N) task_core_num = 0; + return task_core_num; +} + +void lock_core_by_job(struct Job* p) { + for (int i = 0; i < task_core_num; i++) { + int iA = task_array_id[i]; + core_jobs[iA] = p; + } + core_usage += task_core_num; + task_core_num = 0; +} + +void unlock_core_by_job(struct Job* p) { + if (p == NULL) return; + for (int i = 0; i < MAX_CORE_NUM; i++) { + if (core_jobs[i] == p) { + core_jobs[i] = NULL; + core_usage--; + } + } +} + +int set_task_cores(struct Job* p) { + if (p == NULL || p->pid <= 0 || p->state != RUNNING) return -1; + int N = p->num_slots; + + if (allocate_cores(N) != N) { + printf("cannot allocate %d cores\n", N); + return -1; + } + lock_core_by_job(p); + + char* core_str = ints_to_chars(N, task_cores_id, ","); + int size = strlen(core_str) + 25; + char* cmd = (char*) malloc(sizeof(char) * size); + sprintf(cmd, "taskset -cp %s ", core_str); + printf("[CMD] %s %d\n", cmd, p->pid); + + kill_pid(p->pid, cmd); + + free(core_str); + free(cmd); + return 0; +} + + From d03041bdd3a8631840d564587b5851a8892666dd Mon Sep 17 00:00:00 2001 From: kylin Date: Sun, 26 Mar 2023 13:05:36 +0800 Subject: [PATCH 53/91] optimzied the code --- kill_ppid.sh | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/kill_ppid.sh b/kill_ppid.sh index d3c4904..87532b3 100755 --- a/kill_ppid.sh +++ b/kill_ppid.sh @@ -39,12 +39,28 @@ if [[ "$owner" != "$user" ]]; then extra="sudo" fi -for pid in ${pids}; -do + +if [ -z "$3" ] +then if [ -z "$2" ] then - echo "${extra} ${pid}" + for pid in ${pids}; + do + ${extra} echo ${pid} + done else - ${extra} $2 ${pid} + for pid in ${pids}; + do + ${extra} $2 ${pid} + done fi -done +else + for pid in ${pids}; + do + ${extra} $2 ${pid} + ${extra} $3 ${pid} + done +fi + + + From 98b0b2fd014fc0c60d16e611781175ba2a5a2caa Mon Sep 17 00:00:00 2001 From: kylin Date: Sun, 26 Mar 2023 23:00:17 +0800 Subject: [PATCH 54/91] fixed the bug on the job and add more output on info --- Makefile | 2 +- client.c | 16 ++++---- jobs.c | 104 ++++++++++++++++++++++++++++++++++--------------- list.c | 5 +-- main.c | 15 +++++-- main.h | 14 ++++--- server.c | 33 ++++++++++++++-- server_start.c | 40 ++++++++++++------- sqlite.c | 1 + taskset.c | 25 ++++++++---- user.c | 19 +++++---- 11 files changed, 191 insertions(+), 83 deletions(-) diff --git a/Makefile b/Makefile index b161c48..6aa13bb 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ PREFIX?=/usr/local PREFIX_LOCAL=~ GLIBCFLAGS=-D_XOPEN_SOURCE=500 -D__STRICT_ANSI__ CPPFLAGS+=$(GLIBCFLAGS) -CFLAGS?=-pedantic -ansi -Wall -g -O0 -std=gnu11 +CFLAGS?=-pedantic -ansi -Wall -g -O0 -std=gnu11 -DTASKSET OBJECTS=main.o \ server.o \ server_start.o \ diff --git a/client.c b/client.c index 79d2591..b442e29 100644 --- a/client.c +++ b/client.c @@ -501,7 +501,7 @@ void c_pause_job(int jobid) { m.jobid = jobid; send_msg(server_socket, &m); c_wait_server_lines(); - kill_pid(pid, "kill -s STOP"); + kill_pid(pid, "kill -s STOP", NULL); } void c_rerun_job(int jobid) { @@ -525,11 +525,12 @@ void c_rerun_job(int jobid) { // not error, restart job if (wait_server_lines_and_check("Error") == 0) { c_wait_server_lines(); - kill_pid(pid, "kill -s CONT"); + kill_pid(pid, "kill -s CONT", NULL); } } + int c_tail() { char *str; int pid; @@ -594,13 +595,14 @@ void c_kill_job() { void c_kill_all_jobs() { struct Msg m = default_msg(); - if (client_uid != 0) { - printf("Only the root can shutdown the ts server\n"); - return; - } int res; char buf[10]; - printf("Do you want to kill all jobs? (Yes/n) "); + if (client_uid == 0) { + printf("Do you want to kill all jobs in [Root]? (Yes/n) "); + } else { + printf("Do you want to kill all jobs by this user? (Yes/n) "); + } + scanf("%3s", buf); if (strcmp(buf, "Yes") != 0) { return; diff --git a/jobs.c b/jobs.c index fc3853b..05febf6 100644 --- a/jobs.c +++ b/jobs.c @@ -64,6 +64,9 @@ static void destroy_job(struct Job *p) { free(p->depend_on); free(p->label); free(p); + #ifdef TASKSET + free(p->cores); + #endif } } @@ -74,19 +77,36 @@ static void free_cores(struct Job* p) { busy_slots -= p->num_slots; // user_queue[ts_UID]--; user_jobs[ts_UID]--; +#ifdef TASKSET unlock_core_by_job(p); +#endif } -static int allocate_cores(struct Job* p) { - if (p == NULL) return 0; +static void allocate_cores(struct Job* p) { + if (p == NULL) return; int ts_UID = p->ts_UID; user_busy[ts_UID] += p->num_slots; busy_slots += p->num_slots; // user_queue[ts_UID]++; user_jobs[ts_UID]++; - return set_task_cores(p); +#ifdef TASKSET + set_task_cores(p, NULL); +#endif } +static void allocate_cores_and_cont(struct Job* p) { + if (p == NULL) return; + int ts_UID = p->ts_UID; + user_busy[ts_UID] += p->num_slots; + busy_slots += p->num_slots; + // user_queue[ts_UID]++; + user_jobs[ts_UID]++; +#ifdef TASKSET + set_task_cores(p, "kill -s CONT"); +#endif +} + + void send_list_line(int s, const char *str) { struct Msg m = default_msg(); @@ -155,7 +175,7 @@ static struct Job *find_previous_job(const struct Job *final) { } -static struct Job *findjob(int jobid) { +struct Job *findjob(int jobid) { struct Job *p; /* Show Queued or Running jobs */ @@ -290,22 +310,22 @@ static void add_notify_errorlevel_to(struct Job *job, int jobid) { job->notify_errorlevel_to[job->notify_errorlevel_to_size - 1] = jobid; } -void s_kill_all_jobs(int s) { +void s_kill_all_jobs(int s, int ts_UID) { struct Job *p; - s_count_running_jobs(s); + s_count_running_jobs(s, ts_UID); /* send running job PIDs */ p = firstjob.next; while (p != 0) { - if (p->state == RUNNING) + if (p->state == RUNNING && (ts_UID == 0 || p->ts_UID == ts_UID)) send(s, &p->pid, sizeof(int), 0); - + p = p->next; } } -void s_count_running_jobs(int s) { +void s_count_running_jobs(int s, int ts_UID) { int count = 0; struct Job *p; struct Msg m = default_msg(); @@ -313,7 +333,7 @@ void s_count_running_jobs(int s) { /* Count running jobs */ p = firstjob.next; while (p != 0) { - if (p->state == RUNNING) + if (p->state == RUNNING && (ts_UID == 0 || p->ts_UID == ts_UID)) ++count; p = p->next; @@ -571,7 +591,13 @@ static struct Job *newjobptr() { p->next->command = NULL; p->next->work_dir = NULL; p->next->command_strip = 0; - + p->next->depend_on = NULL; + p->next->notify_errorlevel_to = NULL; + #ifdef TASKSET + p->next->cores = NULL; + #endif + + struct Procinfo* info= &(p->next->info); info->enqueue_time.tv_sec = 0; info->start_time.tv_sec = 0; @@ -974,7 +1000,9 @@ static void new_finished_job(struct Job *j) { delete_DB(j->jobid, "Jobs"); insert_DB(j, "Finished"); - unlock_core_by_job(j); + #ifdef TASKSET + unlock_core_by_job(j); + #endif } static int job_is_in_state(int jobid, enum Jobstate state) { @@ -1007,16 +1035,17 @@ static int in_notify_list(int jobid) { return 0; } +/* job_finished from running to jobid */ void job_finished(const struct Result *result, int jobid) { struct Job *p; - if (busy_slots <= 0) + if (busy_slots < 0) error( "Wrong state in the server. busy_slots = %i instead of greater than 0", busy_slots); p = findjob(jobid); - if (p == 0) + if (p == NULL) error("on jobid %i finished, it doesn't exist", jobid); /* The job may be not only in running state, but also in other states, as @@ -1033,6 +1062,7 @@ void job_finished(const struct Result *result, int jobid) { p->state = SKIPPED; else p->state = FINISHED; + p->result = *result; last_finished_jobid = p->jobid; notify_errorlevel(p); @@ -1139,7 +1169,9 @@ static void s_add_job(struct Job* j, struct Job** p) { int slots = j->num_slots; user_busy[ts_UID] += slots; busy_slots += slots; - set_task_cores(j); + #ifdef TASKSET + set_task_cores(j, NULL); + #endif } jobDB_Jobs[jobDB_num] = j; @@ -1261,7 +1293,9 @@ void s_process_runjob_ok(int jobid, char *oname, int pid) { pinfo_set_start_time(&p->info); insert_or_replace_DB(p, "Jobs"); - set_task_cores(p); + #ifdef TASKSET + set_task_cores(p, NULL); + #endif write_logfile(p); } @@ -1329,6 +1363,8 @@ void s_job_info(int s, int jobid) { } m.type = INFO_DATA; + + float t; send_msg(s, &m); pinfo_dump(&p->info, s); fd_nprintf(s, 100, "Command: "); @@ -1338,26 +1374,32 @@ void s_job_info(int s, int jobid) { fd_nprintf(s, 100, ",%i", p->depend_on[i]); fd_nprintf(s, 100, "]&& "); } - write(s, p->command, strlen(p->command)); + write(s, p->command + p->command_strip, strlen(p->command + p->command_strip)); fd_nprintf(s, 100, "\n"); fd_nprintf(s, 100, "User: %s [%d]\n", user_name[p->ts_UID], user_UID[p->ts_UID]); - fd_nprintf(s, 100, "Slots required: %4i; PID: %d\n", - p->num_slots, p->pid); + fd_nprintf(s, 100, "State: %-7s PID: %-6d\n", + jstate2string(p->state), p->pid); + + #ifdef TASKSET + if (p->cores != NULL) { + fd_nprintf(s, 100, "Slots: %-3d \tTaskset: %s\n", p->num_slots, p->cores); + } + #elif + fd_nprintf(s, 100, "Slots: %-3d\n", p->num_slots); + #endif fd_nprintf(s, 100, "Output: %s\n", p->output_filename); fd_nprintf(s, 100, "Enqueue time: %s", ctime(&p->info.enqueue_time.tv_sec)); + fd_nprintf(s, 100, "Start time: %s", ctime(&p->info.start_time.tv_sec)); if (p->state == RUNNING) { - fd_nprintf(s, 100, "Start time: %s", ctime(&p->info.start_time.tv_sec)); - float t = pinfo_time_until_now(&p->info); - char *unit = time_rep(&t); - fd_nprintf(s, 100, "Time running: %f%s\n", t, unit); + t = pinfo_time_until_now(&p->info); } else if (p->state == FINISHED) { - fd_nprintf(s, 100, "Start time: %s", ctime(&p->info.start_time.tv_sec)); + t = pinfo_time_run(&p->info); fd_nprintf(s, 100, "End time: %s", ctime(&p->info.end_time.tv_sec)); - float t = pinfo_time_run(&p->info); - char *unit = time_rep(&t); - fd_nprintf(s, 100, "Time run: %:.4f %s\n", t, unit); } - fd_nprintf(s, 100, "\n"); + char *unit = time_rep(&t); + fd_nprintf(s, 100, "Time running: %.4f %s\n", t, unit); + + // fd_nprintf(s, 100, "\n"); } void s_send_last_id(int s) { @@ -1399,9 +1441,9 @@ void s_cont_user(int s, int ts_UID) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { if (check_ifsleep(p->pid) == 1) { - allocate_cores(p); + allocate_cores_and_cont(p); } - kill_pid(p->pid, "kill -s CONT"); + // kill_pid(p->pid, "kill -s CONT"); } } p = p->next; @@ -1427,7 +1469,7 @@ void s_stop_user(int s, int ts_UID) { if (check_ifsleep(p->pid) == 0) { free_cores(p); } - kill_pid(p->pid, "kill -s STOP"); + kill_pid(p->pid, "kill -s STOP", NULL); } else { char *label = "(...)"; if (p->label != NULL) diff --git a/list.c b/list.c index 1fb436f..b1572b0 100644 --- a/list.c +++ b/list.c @@ -83,10 +83,9 @@ char *joblist_headers() { line = malloc(256); snprintf(line, 256, - "%-4s %-7s %-7s %-6s %-10s %6s %-20s %s [run=%i/%i %d %.2f%%] %s\n", + "%-4s %-7s %-7s %-6s %-10s %6s %-20s %s [run=%i/%i %.2f%%] Core: %-3d %s\n", "ID", "State", "Proc.", "User", "Label", "Time", "Command", "Log", - busy_slots, max_slots, core_usage, 100.0 * busy_slots / max_slots, extra); - + busy_slots, max_slots, 100.0 * busy_slots / max_slots, core_usage, extra); return line; } diff --git a/main.c b/main.c index b05ffd8..9353397 100644 --- a/main.c +++ b/main.c @@ -130,7 +130,7 @@ static struct option longOptions[] = { {"count_running", no_argument, NULL, 'R'}, {"help", no_argument, NULL, 0}, {"last_queue_id", no_argument, NULL, 'q'}, - {"full_cmd", optional_argument, NULL, 'F'}, + {"full_cmd", required_argument, NULL, 'F'}, {"plain", no_argument, NULL, 0}, {"get_logdir", no_argument, NULL, 0}, {"set_logdir", required_argument, NULL, 0}, @@ -144,7 +144,7 @@ static struct option longOptions[] = { {"lock-ts", no_argument, NULL, 0}, {"unlock-ts", no_argument, NULL, 0}, {"daemon", no_argument, NULL, 0}, - {"pid", required_argument, NULL, 0}, + {"relink", required_argument, NULL, 0}, {"stime", required_argument, NULL, 0}, {"check_daemon", no_argument, NULL, 0}, {NULL, 0, NULL, 0}}; @@ -208,7 +208,14 @@ void parse_opts(int argc, char **argv) { } else if (strcmp(longOptions[optionIdx].name, "plain") == 0) { command_line.request = c_LIST; command_line.plain_list = 1; - } else if (strcmp(longOptions[optionIdx].name, "pid") == 0) { + } else if (strcmp(longOptions[optionIdx].name, "full_cmd") == 0) { + command_line.request = c_SHOW_CMD; + if (optarg != NULL) { + command_line.jobid = -1; // str2int(optarg); + } else { + command_line.jobid = -1; + } + } else if (strcmp(longOptions[optionIdx].name, "relink") == 0) { command_line.taskpid = str2int(optarg); if (command_line.taskpid <= 0) command_line.taskpid = 0; @@ -583,7 +590,7 @@ static void print_help(const char *cmd) { printf(" --cont [user] For normal user, continue all " "paused tasks and lock the account. \n " " For root, to unlock all users or single [user].\n"); - printf(" --pid [PID] Relink the running tasks by its [PID] from an expected failure.\n"); + printf(" --relink [PID] Relink the running tasks by its [PID] from an expected failure.\n"); printf(" --stime [start_time] Set the relinked task by starting time (Unix epoch).\n"); printf("Actions:\n"); printf(" -A Show all users information\n"); diff --git a/main.h b/main.h index 03ff0a6..63c214a 100644 --- a/main.h +++ b/main.h @@ -230,6 +230,9 @@ struct Job { char *label; struct Procinfo info; int num_slots; +#ifdef TASKSET + char* cores; +#endif }; enum ExitCodes { @@ -356,7 +359,7 @@ void s_send_state(int s, int jobid); void s_swap_jobs(int s, int jobid1, int jobid2); -void s_count_running_jobs(int s); +void s_count_running_jobs(int s, int ts_UID); void dump_jobs_struct(FILE *out); @@ -384,7 +387,7 @@ int wake_hold_client(); void s_get_label(int s, int jobid); -void s_kill_all_jobs(int s); +void s_kill_all_jobs(int s, int ts_UID); void s_get_logdir(int s); @@ -512,7 +515,7 @@ int tail_file(const char *fname, int last_lines); /* user.c */ static const int root_UID = 0; -char *get_kill_sh_path(); +const char *get_kill_sh_path(); void read_user_file(const char *path); int get_tsUID(int uid); void c_refresh_user(); @@ -524,7 +527,7 @@ long str2int(const char *str); void debug_write(const char *str); const char *uid2user_name(int uid); int read_first_jobid_from_logfile(const char *path); -void kill_pid(int ppid, const char *signal); +void kill_pid(int ppid, const char *signal, const char* extra); char* linux_cmd(char* CMD, char* out, int out_size); char **split_str(const char *str, int *size); void check_running_task(int pid); @@ -556,6 +559,7 @@ void s_sort_jobs(); int s_check_relink(int s, struct Msg *m, int ts_UID); void s_read_sqlite(); int s_check_running_pid(int pid); +struct Job *findjob(int jobid); /* client.c */ void c_list_jobs_all(); @@ -589,6 +593,6 @@ int* chars_to_ints(int *size, char* str, const char* delim); /* taskset.c */ void init_taskset(); -int set_task_cores(struct Job* p); +int set_task_cores(struct Job* p, const char* extra); void unlock_core_by_job(struct Job* p); diff --git a/server.c b/server.c index 04e9c9a..734f91a 100644 --- a/server.c +++ b/server.c @@ -416,6 +416,7 @@ static void remove_connection(int index) { nconnections--; } + static void clean_after_client_disappeared(int socket, int index) { /* Act as if the job ended. */ int jobid = client_cs[index].jobid; @@ -447,6 +448,25 @@ static void clean_after_client_disappeared(int socket, int index) { remove_connection(index); } + +static void s_remove_all_queues(int ts_UID) { + int i = 0; + struct Job* p; + while(i < nconnections - 1) { + if (ts_UID == 0 || client_cs[i].ts_UID == ts_UID) { + p = findjob(client_cs[i].jobid); + if (p->state != RUNNING) { + pinfo_set_start_time(&p->info); + clean_after_client_disappeared(client_cs[i].socket, i); + } else { + i++; // To next one + } + } else { + i++; // To next one + } + } +} + static enum Break client_read(int index) { struct Msg m = default_msg(); int s; @@ -576,9 +596,14 @@ static enum Break client_read(int index) { s_process_runjob_ok(client_cs[index].jobid, buffer, m.u.output.pid); } break; case KILL_ALL: - if (ts_UID == 0) - s_kill_all_jobs(s); - break; + s_kill_all_jobs(s, ts_UID); + s_remove_all_queues(ts_UID); + /* TODO to remove the queued jobs + for (int i = 0; i < nconnections; i++) { + client_cs[].hasjob = 0; + clean_after_client_disappeared(s, index); + } + */ case LIST: term_width = m.u.list.term_width; if (m.u.list.plain_list) @@ -657,7 +682,7 @@ static enum Break client_read(int index) { s_wait_running_job(s, m.jobid); break; case COUNT_RUNNING: - s_count_running_jobs(s); + s_count_running_jobs(s, ts_UID); break; case URGENT: if (ts_UID == 0 || ts_UID == s_get_job_tsUID(m.jobid)) { diff --git a/server_start.c b/server_start.c index 3dfa260..8fc5cd5 100644 --- a/server_start.c +++ b/server_start.c @@ -19,26 +19,30 @@ #include "main.h" int server_socket; +char kill_sh_path[1024] = {0}; static char *socket_path; static int should_check_owner = 0; static int fork_server(); -char *get_kill_sh_path() { +const char *get_kill_sh_path() { return kill_sh_path; } + +static const char *set_kill_sh_path() { char *tmpdir; tmpdir = getenv("TMPDIR"); if (tmpdir == NULL) tmpdir = "/tmp"; - char *path = NULL; + // char *path = NULL; int size = strlen(tmpdir) + strlen("/kill_ppid.sh") + 1; - path = (char *)malloc(size); - snprintf(path, size, "%s/kill_ppid.sh", tmpdir); - return path; + // path = (char *)malloc(size); + snprintf(kill_sh_path, size, "%s/kill_ppid.sh", tmpdir); + return kill_sh_path; } static void setup_kill_sh() { - char *path = get_kill_sh_path(); + set_kill_sh_path(); + const char* path = get_kill_sh_path(); FILE *f = fopen(path, "w"); if (f == NULL) { printf("Cannot create `kill_ppide.sh` file at %s\n", path); @@ -85,21 +89,31 @@ if [[ "$owner" != "$user" ]]; then extra="sudo" fi -for pid in ${pids}; -do +if [ -z "$3" ] +then if [ -z "$2" ] then - echo "${extra} ${pid}" + for pid in ${pids}; + do + ${extra} echo ${pid} + done else - ${extra} $2 ${pid} + for pid in ${pids}; + do + ${extra} $2 ${pid} + done fi -done - +else + for pid in ${pids}; + do + ${extra} $2 ${pid} + ${extra} $3 ${pid} + done +fi )"); fclose(f); printf(" Kill_PPID.sh at `%s`\n\n", path); - free(path); } void create_socket_path(char **path) { diff --git a/sqlite.c b/sqlite.c index abbca0e..4b2e362 100644 --- a/sqlite.c +++ b/sqlite.c @@ -349,6 +349,7 @@ int read_jobid_DB(int** jobids, const char* table) { struct Job* read_DB(int jobid, const char* table) { struct Job* job = (struct Job*)malloc(sizeof(struct Job)*1); + job->cores = NULL; struct Result* result = &(job->result); struct Procinfo* info= &(job->info); diff --git a/taskset.c b/taskset.c index 551380f..87c561c 100644 --- a/taskset.c +++ b/taskset.c @@ -15,15 +15,16 @@ int task_array_id[MAX_CORE_NUM] = {0}; int task_core_num, core_usage; void init_taskset() { - printf("CPU taskset()\n"); + // printf("CPU taskset()\n"); task_core_num = 0; core_usage = 0; - + #ifdef TASKSET for (int i = 0; i < MAX_CORE_NUM; i++) { core_id[i] = i / 2 + ((i % 2) + (i >= MAX_CORE_NUM_HALF)) * MAX_CORE_NUM_QUAD; printf("[%3d] => %3d\t", i, core_id[i]); if ((i+1)%8 == 0) printf("\n"); } + #endif } int allocate_cores(int N) { @@ -59,10 +60,13 @@ void unlock_core_by_job(struct Job* p) { core_usage--; } } + free(p->cores); + p->cores = NULL; } -int set_task_cores(struct Job* p) { +int set_task_cores(struct Job* p, const char* extra) { if (p == NULL || p->pid <= 0 || p->state != RUNNING) return -1; +#ifdef TASKSET int N = p->num_slots; if (allocate_cores(N) != N) { @@ -72,15 +76,20 @@ int set_task_cores(struct Job* p) { lock_core_by_job(p); char* core_str = ints_to_chars(N, task_cores_id, ","); - int size = strlen(core_str) + 25; + int size = strlen(core_str) + 30; char* cmd = (char*) malloc(sizeof(char) * size); sprintf(cmd, "taskset -cp %s ", core_str); - printf("[CMD] %s %d\n", cmd, p->pid); - - kill_pid(p->pid, cmd); + if (extra == NULL) { + printf("[CMD] %s %d\n", cmd, p->pid); + } else { + printf("[CMD] %s %d; %s %d\n", cmd, p->pid, extra, p->pid); + } - free(core_str); + kill_pid(p->pid, cmd, extra); + p->cores = core_str; + // free(core_str); free(cmd); +#endif return 0; } diff --git a/user.c b/user.c index 3b259f2..13e4e07 100644 --- a/user.c +++ b/user.c @@ -294,13 +294,18 @@ int get_tsUID(int uid) { return -1; } -void kill_pid(int pid, const char *signal) { - FILE *fp; + +void kill_pid(int pid, const char *signal, const char* extra) { char command[1024]; - char *path = get_kill_sh_path(); - sprintf(command, "bash %s %d \"%s\"", path, pid, signal); + const char *path = get_kill_sh_path(); + if (extra == NULL) { + sprintf(command, "bash %s %d \"%s\"", path, pid, signal); + } else { + sprintf(command, "bash %s %d \"%s\" \"%s\"", path, pid, signal, extra); + } // printf("command = %s\n", command); - fp = popen(command, "r"); - free(path); - pclose(fp); + // fp = popen(command, "r"); + system(command); + // pclose(fp); } + From 6753fd627386864f2632890f4b1a551eea84b0d3 Mon Sep 17 00:00:00 2001 From: kylin Date: Mon, 27 Mar 2023 11:48:38 +0800 Subject: [PATCH 55/91] fixed the bug on the job and add cjson --- Makefile | 4 + cjson/cJSON.c | 3119 +++++++++++++++++++++++++++++++++++++++++++++++++ cjson/cJSON.h | 300 +++++ client.c | 11 +- execute.c | 12 +- jobs.c | 282 ++++- list.c | 79 +- main.c | 50 +- main.h | 16 +- server.c | 21 +- sqlite.c | 24 +- taskset.c | 2 +- ttail.c | 0 user.c | 5 +- 14 files changed, 3794 insertions(+), 131 deletions(-) create mode 100644 cjson/cJSON.c create mode 100644 cjson/cJSON.h delete mode 100644 ttail.c diff --git a/Makefile b/Makefile index 6aa13bb..d215c1e 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,7 @@ OBJECTS=main.o \ env.o \ tail.o \ user.o \ + cJSON.o \ sqlite.o \ taskset.o TARGET=ts @@ -54,8 +55,11 @@ error.o: error.c main.h signals.o: signals.c main.h list.o: list.c main.h tail.o: tail.c main.h +cJSON.o: cjson/cJSON.c cjson/cJSON.h sqlite.o: sqlite.c main.h taskset.o: taskset.c main.h +cJSON.o : cjson/cJSON.c cjson/cJSON.h + $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ clean: rm -f *.o $(TARGET); killall ts; rm ts; diff --git a/cjson/cJSON.c b/cjson/cJSON.c new file mode 100644 index 0000000..524ba46 --- /dev/null +++ b/cjson/cJSON.c @@ -0,0 +1,3119 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning (push) +/* disable warning about single line comments in system headers */ +#pragma warning (disable : 4001) +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_LOCALES +#include +#endif + +#if defined(_MSC_VER) +#pragma warning (pop) +#endif +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#ifdef true +#undef true +#endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) + +/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */ +#ifndef isinf +#define isinf(d) (isnan((d - d)) && !isnan(d)) +#endif +#ifndef isnan +#define isnan(d) (d != d) +#endif + +#ifndef NAN +#ifdef _WIN32 +#define NAN sqrt(-1.0) +#else +#define NAN 0.0/0.0 +#endif +#endif + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) +{ + if (!cJSON_IsString(item)) + { + return NULL; + } + + return item->valuestring; +} + +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) +{ + if (!cJSON_IsNumber(item)) + { + return (double) NAN; + } + + return item->valuedouble; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 15) + #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char*) cJSON_Version(void) +{ + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks +{ + void *(CJSON_CDECL *allocate)(size_t size); + void (CJSON_CDECL *deallocate)(void *pointer); + void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dllimport '...' is not static */ +static void * CJSON_CDECL internal_malloc(size_t size) +{ + return malloc(size); +} +static void CJSON_CDECL internal_free(void *pointer) +{ + free(pointer); +} +static void * CJSON_CDECL internal_realloc(void *pointer, size_t size) +{ + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* strlen of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; + +static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) +{ + size_t length = 0; + unsigned char *copy = NULL; + + if (string == NULL) + { + return NULL; + } + + length = strlen((const char*)string) + sizeof(""); + copy = (unsigned char*)hooks->allocate(length); + if (copy == NULL) + { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (hooks == NULL) + { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; + return; + } + + global_hooks.allocate = malloc; + if (hooks->malloc_fn != NULL) + { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != NULL) + { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) + { + global_hooks.reallocate = realloc; + } +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) +{ + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) + { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ + cJSON *next = NULL; + while (item != NULL) + { + next = item->next; + if (!(item->type & cJSON_IsReference) && (item->child != NULL)) + { + cJSON_Delete(item->child); + } + if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) + { + global_hooks.deallocate(item->valuestring); + } + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + global_hooks.deallocate(item->string); + } + global_hooks.deallocate(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct +{ + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ + double number = 0; + unsigned char *after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) + { + switch (buffer_at_offset(input_buffer)[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) + { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ + if (number >= INT_MAX) + { + object->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + object->valueint = INT_MIN; + } + else + { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) +{ + char *copy = NULL; + /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */ + if (!(object->type & cJSON_String) || (object->type & cJSON_IsReference)) + { + return NULL; + } + if (strlen(valuestring) <= strlen(object->valuestring)) + { + strcpy(object->valuestring, valuestring); + return object->valuestring; + } + copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks); + if (copy == NULL) + { + return NULL; + } + if (object->valuestring != NULL) + { + cJSON_free(object->valuestring); + } + object->valuestring = copy; + + return copy; +} + +typedef struct +{ + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, size_t needed) +{ + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) + { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return NULL; + } + } + else + { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) + { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == NULL) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } + else + { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + + memcpy(newbuffer, p->buffer, p->offset + 1); + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); + return (fabs(a - b) <= maxVal * DBL_EPSILON); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if (output_buffer == NULL) + { + return false; + } + + /* This checks for NaN and Infinity */ + if (isnan(d) || isinf(d)) + { + length = sprintf((char*)number_buffer, "null"); + } + else if(d == (double)item->valueint) + { + length = sprintf((char*)number_buffer, "%d", item->valueint); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occurred */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == NULL) + { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') + { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) + { + /* is escape sequence */ + if (input_end[0] == '\\') + { + if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) + { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) + { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); + if (output == NULL) + { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) + { + if (*input_pointer != '\\') + { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else + { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) + { + goto fail; + } + + switch (input_pointer[1]) + { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) + { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char*)output; + + input_buffer->offset = (size_t) (input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if (output != NULL) + { + input_buffer->hooks.deallocate(output); + } + + if (input_pointer != NULL) + { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) + { + return false; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) + { + return NULL; + } + + if (cannot_access_at_index(buffer, 0)) + { + return buffer; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) + { + return NULL; + } + + if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) + { + buffer->offset += 3; + } + + return buffer; +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + size_t buffer_length; + + if (NULL == value) + { + return NULL; + } + + /* Adding null character size due to require_null_terminated. */ + buffer_length = strlen(value) + sizeof(""); + + return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL || 0 == buffer_length) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = buffer_length; + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != NULL) + { + cJSON_Delete(item); + } + + if (value != NULL) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) +{ + return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); +} + +#define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) + { + printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + buffer->buffer = NULL; + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + } + + return printed; + +fail: + if (buffer->buffer != NULL) + { + hooks->deallocate(buffer->buffer); + } + + if (printed != NULL) + { + hooks->deallocate(printed); + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char*)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) + { + return NULL; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) + { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) + { + global_hooks.deallocate(p.buffer); + return NULL; + } + + return (char*)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if ((length < 0) || (buffer == NULL)) + { + return false; + } + + p.buffer = (unsigned char*)buffer; + p.length = (size_t)length; + p.offset = 0; + p.noalloc = true; + p.format = format; + p.hooks = global_hooks; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) + { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) + { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) + { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) + { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) + { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) + { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) + { + return parse_object(item, input_buffer); + } + + return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) + { + return false; + } + + switch ((item->type) & 0xFF) + { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == NULL) + { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) + { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') + { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) + { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') + { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) + { + if (!print_value(current_element, output_buffer)) + { + return false; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) + { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) + { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) + { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) + { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) + { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output: */ + length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) + { + return false; + } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = NULL; + size_t size = 0; + + if (array == NULL) + { + return 0; + } + + child = array->child; + + while(child != NULL) + { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON* get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) + { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) + { + return NULL; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + if ((current_element == NULL) || (current_element->string == NULL)) { + return NULL; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) +{ + cJSON *reference = NULL; + if (item == NULL) + { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if (reference == NULL) + { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) +{ + cJSON *child = NULL; + + if ((item == NULL) || (array == NULL) || (array == item)) + { + return false; + } + + child = array->child; + /* + * To find the last item in array quickly, we use prev in array + */ + if (child == NULL) + { + /* list is empty, start new one */ + array->child = item; + item->prev = item; + item->next = NULL; + } + else + { + /* append to the end */ + if (child->prev) + { + suffix_object(child->prev, item); + array->child->prev = item; + } + } + + return true; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ + return add_item_to_array(array, item); +} + +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void* cast_away_const(const void* string) +{ + return (void*)string; +} +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic pop +#endif + + +static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key) +{ + char *new_key = NULL; + int new_type = cJSON_Invalid; + + if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item)) + { + return false; + } + + if (constant_key) + { + new_key = (char*)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } + else + { + new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); + if (new_key == NULL) + { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ + if (array == NULL) + { + return false; + } + + return add_item_to_array(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ + if ((object == NULL) || (string == NULL)) + { + return false; + } + + return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) +{ + cJSON *null = cJSON_CreateNull(); + if (add_item_to_object(object, name, null, &global_hooks, false)) + { + return null; + } + + cJSON_Delete(null); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name) +{ + cJSON *true_item = cJSON_CreateTrue(); + if (add_item_to_object(object, name, true_item, &global_hooks, false)) + { + return true_item; + } + + cJSON_Delete(true_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name) +{ + cJSON *false_item = cJSON_CreateFalse(); + if (add_item_to_object(object, name, false_item, &global_hooks, false)) + { + return false_item; + } + + cJSON_Delete(false_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) +{ + cJSON *bool_item = cJSON_CreateBool(boolean); + if (add_item_to_object(object, name, bool_item, &global_hooks, false)) + { + return bool_item; + } + + cJSON_Delete(bool_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) +{ + cJSON *number_item = cJSON_CreateNumber(number); + if (add_item_to_object(object, name, number_item, &global_hooks, false)) + { + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) +{ + cJSON *string_item = cJSON_CreateString(string); + if (add_item_to_object(object, name, string_item, &global_hooks, false)) + { + return string_item; + } + + cJSON_Delete(string_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) +{ + cJSON *raw_item = cJSON_CreateRaw(raw); + if (add_item_to_object(object, name, raw_item, &global_hooks, false)) + { + return raw_item; + } + + cJSON_Delete(raw_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name) +{ + cJSON *object_item = cJSON_CreateObject(); + if (add_item_to_object(object, name, object_item, &global_hooks, false)) + { + return object_item; + } + + cJSON_Delete(object_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name) +{ + cJSON *array = cJSON_CreateArray(); + if (add_item_to_object(object, name, array, &global_hooks, false)) + { + return array; + } + + cJSON_Delete(array); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ + if ((parent == NULL) || (item == NULL)) + { + return NULL; + } + + if (item != parent->child) + { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != NULL) + { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) + { + /* first element */ + parent->child = item->next; + } + else if (item->next == NULL) + { + /* last element */ + parent->child->prev = item->prev; + } + + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ + if (which < 0) + { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ + cJSON *after_inserted = NULL; + + if (which < 0) + { + return false; + } + + after_inserted = get_array_item(array, (size_t)which); + if (after_inserted == NULL) + { + return add_item_to_array(array, newitem); + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ + if ((parent == NULL) || (replacement == NULL) || (item == NULL)) + { + return false; + } + + if (replacement == item) + { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != NULL) + { + replacement->next->prev = replacement; + } + if (parent->child == item) + { + if (parent->child->prev == parent->child) + { + replacement->prev = replacement; + } + parent->child = replacement; + } + else + { /* + * To find the last item in array quickly, we use prev in array. + * We can't modify the last item's next pointer where this item was the parent's child + */ + if (replacement->prev != NULL) + { + replacement->prev->next = replacement; + } + if (replacement->next == NULL) + { + parent->child->prev = replacement; + } + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ + if (which < 0) + { + return false; + } + + return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ + if ((replacement == NULL) || (string == NULL)) + { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) + { + cJSON_free(replacement->string); + } + replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if (replacement->string == NULL) + { + return false; + } + + replacement->type &= ~cJSON_StringIsConst; + + return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = boolean ? cJSON_True : cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_String; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) + { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char*)cast_away_const(string); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Raw; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type=cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) + { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (strings == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +/* Duplication */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) + { + newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != NULL) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + if (newitem && newitem->child) + { + newitem->child->prev = newchild; + } + + return newitem; + +fail: + if (newitem != NULL) + { + cJSON_Delete(newitem); + } + + return NULL; +} + +static void skip_oneline_comment(char **input) +{ + *input += static_strlen("//"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if ((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char **input) +{ + *input += static_strlen("/*"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if (((*input)[0] == '*') && ((*input)[1] == '/')) + { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char **input, char **output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + + for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if ((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + char *into = json; + + if (json == NULL) + { + return; + } + + while (json[0] != '\0') + { + switch (json[0]) + { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if (json[1] == '/') + { + skip_oneline_comment(&json); + } + else if (json[1] == '*') + { + skip_multiline_comment(&json); + } else { + json++; + } + break; + + case '\"': + minify_string(&json, (char**)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (compare_double(a->valuedouble, b->valuedouble)) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) + { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) + { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); +} diff --git a/cjson/cJSON.h b/cjson/cJSON.h new file mode 100644 index 0000000..95a9cf6 --- /dev/null +++ b/cjson/cJSON.h @@ -0,0 +1,300 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif + +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options: + +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) +CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol + +For *nix builds that support visibility attribute, you can define similar behavior by + +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS + +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does + +*/ + +#define CJSON_CDECL __cdecl +#define CJSON_STDCALL __stdcall + +/* export symbols by default, this is necessary for copy pasting the C and header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type CJSON_STDCALL +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL +#endif +#else /* !__WINDOWS__ */ +#define CJSON_CDECL +#define CJSON_STDCALL + +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 15 + +#include + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks +{ + /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ + void *(CJSON_CDECL *malloc_fn)(size_t sz); + void (CJSON_CDECL *free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char*) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); + +/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* Check item type and return its value */ +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* Create a string where valuestring references a string so + * it will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); +/* Create an object/array that only references it's elements so + * they will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); + +/* These utilities create an Array of count items. + * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before + * writing to `item->string` */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detach items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will + * need to be released. With recurse!=0, it will duplicate any children connected to the item. + * The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + +/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. + * The input pointer json cannot point to a read-only address area, such as a string constant, + * but should point to a readable and writable address area. */ +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Helper functions for creating and adding items to an object at the same time. + * They return the added item or NULL on failure. */ +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) +/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring); + +/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/ +#define cJSON_SetBoolValue(object, boolValue) ( \ + (object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \ + (object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \ + cJSON_Invalid\ +) + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/client.c b/client.c index b442e29..e0dec62 100644 --- a/client.c +++ b/client.c @@ -114,7 +114,7 @@ void c_new_job() { /* Send the environment */ send_bytes(server_socket, myenv, m.u.newjob.env_size); - free(new_command); + // free(new_command); free(myenv); } @@ -178,8 +178,9 @@ int c_wait_server_commands() { result.real_ms = 0.; result.skipped = 1; c_send_runjob_ok(0, -1); - } else + } else { run_job(m.jobid, &result); + } c_end_of_job(&result); return result.errorlevel; } @@ -223,8 +224,9 @@ void c_list_jobs() { struct Msg m = default_msg(); m.type = LIST; - m.u.list.plain_list = command_line.plain_list; + m.u.list.list_format = command_line.list_format; m.u.list.term_width = term_width; + // m.u.term_width = term_width; send_msg(server_socket, &m); } @@ -232,8 +234,9 @@ void c_list_jobs_all() { struct Msg m = default_msg(); m.type = LIST_ALL; - m.u.list.plain_list = command_line.plain_list; + m.u.list.list_format = command_line.list_format; m.u.list.term_width = term_width; + // m.u.list.plain_list = command_line.plain_list; send_msg(server_socket, &m); } diff --git a/execute.c b/execute.c index 1533a0f..c8834a6 100644 --- a/execute.c +++ b/execute.c @@ -80,9 +80,6 @@ static void run_relink(int pid, struct Result *result) { send_mail(command_line.jobid, result->errorlevel, ofname, command); } hook_on_finish(command_line.jobid, result->errorlevel, ofname, command); - free(command); - - free(ofname); /* Calculate times */ gettimeofday(&endtv, NULL); @@ -93,6 +90,9 @@ static void run_relink(int pid, struct Result *result) { * is obtained in POSIX using sysconf(). */ result->user_ms = (float)cpu_times.tms_cutime / (float)sysconf(_SC_CLK_TCK); result->system_ms = (float)cpu_times.tms_cstime / (float)sysconf(_SC_CLK_TCK); + + free(command); + free(ofname); } /* Returns errorlevel */ static void run_parent(int fd_read_filename, int pid, struct Result *result) { @@ -154,9 +154,6 @@ static void run_parent(int fd_read_filename, int pid, struct Result *result) { send_mail(command_line.jobid, result->errorlevel, ofname, command); } hook_on_finish(command_line.jobid, result->errorlevel, ofname, command); - free(command); - - free(ofname); /* Calculate times */ gettimeofday(&endtv, NULL); @@ -167,6 +164,9 @@ static void run_parent(int fd_read_filename, int pid, struct Result *result) { * is obtained in POSIX using sysconf(). */ result->user_ms = (float)cpu_times.tms_cutime / (float)sysconf(_SC_CLK_TCK); result->system_ms = (float)cpu_times.tms_cstime / (float)sysconf(_SC_CLK_TCK); + + free(command); + free(ofname); } void create_closed_read_on(int dest) { diff --git a/jobs.c b/jobs.c index 05febf6..2f91806 100644 --- a/jobs.c +++ b/jobs.c @@ -18,6 +18,7 @@ #include "main.h" #include "user.h" +#include "cjson/cJSON.h" /* The list will access them */ int busy_slots = 0; @@ -63,10 +64,10 @@ static void destroy_job(struct Job *p) { pinfo_free(&p->info); free(p->depend_on); free(p->label); - free(p); #ifdef TASKSET free(p->cores); #endif + free(p); } } @@ -106,6 +107,125 @@ static void allocate_cores_and_cont(struct Job* p) { #endif } +/* Serialize a job and add it to the JSON array. Returns 1 for success, 0 for failure. */ +static int add_job_to_json_array(struct Job *p, cJSON *jobs) { + cJSON *job = cJSON_CreateObject(); + if (job == NULL) + { + error("Error initializing JSON object for job %i.", p->jobid); + return 0; + } + cJSON_AddItemToArray(jobs, job); + + /* Add fields */ + cJSON *field; + + /* ID */ + field = cJSON_CreateNumber(p->jobid); + if (field == NULL) + { + error("Error initializing JSON object for job %i field ID.", p->jobid); + return 0; + } + cJSON_AddItemToObject(job, "ID", field); + + /* State */ + const char *state_string = jstate2string(p->state); + field = cJSON_CreateStringReference(state_string); + if (field == NULL) + { + error("Error initializing JSON object for job %i field State (value %d/%s).", p->jobid, p->state, state_string); + return 0; + } + cJSON_AddItemToObject(job, "State", field); + + /* num_slots */ + field = cJSON_CreateNumber(p->num_slots); + if (field == NULL) + { + error("Error initializing JSON object for job %i field ID.", p->jobid); + return 0; + } + cJSON_AddItemToObject(job, "Proc.", field); + + /* user */ + field = cJSON_CreateStringReference(user_name[p->ts_UID]); + if (field == NULL) + { + error("Error initializing JSON object for job %i field State (value %d/%s).", p->jobid, p->state, state_string); + return 0; + } + cJSON_AddItemToObject(job, "User", field); + + /* label */ + + if (p->label != NULL) { + field = cJSON_CreateStringReference(p->label); + } + else { + field = cJSON_CreateNull(); + } + if (field == NULL) + { + error("Error initializing JSON object for job %i field State (value %d/%s).", p->jobid, p->state, state_string); + return 0; + } + cJSON_AddItemToObject(job, "Label", field); + + /* Output */ + field = cJSON_CreateStringReference(p->output_filename); + if (field == NULL) + { + error("Error initializing JSON object for job %i field Output (value %s).", p->jobid, p->output_filename); + return 0; + } + cJSON_AddItemToObject(job, "Output", field); + + /* E-Level */ + if (p->state == FINISHED) { + field = cJSON_CreateNumber(p->result.errorlevel); + } + else { + field = cJSON_CreateNull(); + } + if (field == NULL) + { + error("Error initializing JSON object for job %i field E-Level.", p->jobid); + return 0; + } + cJSON_AddItemToObject(job, "E-Level", field); + + /* Time */ + if (p->state == FINISHED) { + field = cJSON_CreateNumber(p->result.real_ms); + if (field == NULL) + { + error("Error initializing JSON object for job %i field Time_ms (value %d).", p->result.real_ms); + return 0; + } + } + else { + field = cJSON_CreateNull(); + if (field == NULL) + { + error("Error initializing JSON object for job %i field Time_ms (no result)."); + return 0; + } + } + cJSON_AddItemToObject(job, "Time_ms", field); + + /* Command */ + field = cJSON_CreateStringReference(p->command + p->command_strip); + if (field == NULL) + { + error("Error initializing JSON object for job %i field Command (value %s).", p->jobid, p->command); + return 0; + } + cJSON_AddItemToObject(job, "Command", field); + + return 1; +} + void send_list_line(int s, const char *str) { struct Msg m = default_msg(); @@ -452,73 +572,143 @@ const char *jstate2string(enum Jobstate s) { const char *jobstate; switch (s) { case QUEUED: - jobstate = "queued"; + jobstate = "queued "; break; case RUNNING: - jobstate = "running"; + jobstate = "running "; break; case FINISHED: jobstate = "finished"; break; case SKIPPED: case HOLDING_CLIENT: - jobstate = "skipped"; + jobstate = "skipped "; break; case RELINK: - jobstate = "relink"; + jobstate = "relink "; break; case WAIT: - jobstate = "wait"; + jobstate = "wait "; break; } return jobstate; } -void s_list(int s, int ts_UID) { +void s_list(int s, int ts_UID, enum ListFormat listFormat) { struct Job *p; char *buffer; + if (listFormat == DEFAULT) { + /* Times: 0.00/0.00/0.00 - 4+4+4+2 = 14*/ + buffer = joblist_headers(); + send_list_line(s, buffer); + free(buffer); - /* Times: 0.00/0.00/0.00 - 4+4+4+2 = 14*/ - buffer = joblist_headers(); - send_list_line(s, buffer); - free(buffer); + /* Show Queued or Running jobs */ + p = firstjob.next; + while (p != NULL) { + if (p->state != HOLDING_CLIENT) { + if (p->ts_UID == ts_UID || ts_UID == 0) { + buffer = joblist_line(p); + // sprintf(buf, "== jobid = %d\n", p->jobid); + // send_list_line(s, buf); + send_list_line(s, buffer); + free(buffer); + } + } + p = p->next; + } - /* Show Queued or Running jobs */ - // char buf[256]; - p = firstjob.next; - while (p != 0) { - // sprintf(buf, "jobid = %d\n", p->jobid); - // send_list_line(s, buf); + p = first_finished_job.next; + if (p != NULL && firstjob.next != NULL) + send_list_line(s, "----- Finished -----\n"); - if (p->state != HOLDING_CLIENT) { - if (p->ts_UID == ts_UID) { + /* Show Finished jobs */ + while (p != NULL) { + if (p->ts_UID == ts_UID || ts_UID == 0) { buffer = joblist_line(p); - // sprintf(buf, "== jobid = %d\n", p->jobid); - // send_list_line(s, buf); send_list_line(s, buffer); free(buffer); } + p = p->next; + } + if (ts_UID == 0) { + s_user_status_all(s); + } else { + s_user_status(s, ts_UID); + } + } else if (listFormat == JSON) { + cJSON *jobs = cJSON_CreateArray(); + if (jobs == NULL) + { + error("Error initializing JSON array."); + goto end; + } + /* Serialize Queued or Running jobs */ + p = firstjob.next; + while (p != NULL) { + if (p->state != HOLDING_CLIENT) { + int success = add_job_to_json_array(p, jobs); + if (success == 0) { + goto end; + } + } + p = p->next; } - p = p->next; - } - - p = first_finished_job.next; - if (p != NULL && firstjob.next != NULL) - send_list_line(s, "----- Finished -----\n"); - /* Show Finished jobs */ - while (p != 0) { - if (p->ts_UID == ts_UID) { - buffer = joblist_line(p); - send_list_line(s, buffer); - free(buffer); + /* Serialize Finished jobs */ + p = first_finished_job.next; + while (p != 0) { + int success = add_job_to_json_array(p, jobs); + if (success == 0) { + goto end; + } + p = p->next; } - p = p->next; - } - + + buffer = cJSON_PrintUnformatted(jobs); + if (buffer == NULL) + { + error("Error converting jobs to JSON."); + goto end; + } + + // append newline + size_t buffer_strlen = strlen(buffer); + buffer = realloc(buffer, buffer_strlen+1+1); + strcat(buffer, "\n"); + + send_list_line(s, buffer); + goto end; + + end: + cJSON_Delete(jobs); + free(buffer); + // end of Json + } else if (listFormat == TAB) { + /* Show Queued or Running jobs */ + p = firstjob.next; + while (p != 0) { + if (p->state != HOLDING_CLIENT) { + buffer = joblist_line_plain(p); + send_list_line(s, buffer); + free(buffer); + } + p = p->next; + } + + p = first_finished_job.next; + + /* Show Finished jobs */ + while (p != 0) { + buffer = joblist_line_plain(p); + send_list_line(s, buffer); + free(buffer); + p = p->next; + } + } // end of TAB } -void s_list_all(int s) { +void s_list_all(int s, enum ListFormat listFormat) { struct Job *p; char *buffer; @@ -551,11 +741,12 @@ void s_list_all(int s) { } } +/* void s_list_plain(int s) { struct Job *p; char *buffer; - /* Show Queued or Running jobs */ + / Show Queued or Running jobs / p = firstjob.next; while (p != 0) { if (p->state != HOLDING_CLIENT) { @@ -568,7 +759,7 @@ void s_list_plain(int s) { p = first_finished_job.next; - /* Show Finished jobs */ + / Show Finished jobs / while (p != 0) { buffer = joblist_line_plain(p); send_list_line(s, buffer); @@ -576,6 +767,7 @@ void s_list_plain(int s) { p = p->next; } } +*/ static struct Job *newjobptr() { struct Job *p; @@ -584,7 +776,8 @@ static struct Job *newjobptr() { while (p->next != 0) p = p->next; - p->next = (struct Job *)malloc(sizeof(*p)); + p->next = (struct Job *)calloc(sizeof(*p), sizeof(char)); + /* p->next->next = 0; p->next->output_filename = 0; p->next->pid = 0; @@ -593,6 +786,7 @@ static struct Job *newjobptr() { p->next->command_strip = 0; p->next->depend_on = NULL; p->next->notify_errorlevel_to = NULL; + p->result.errorlevel = 0; #ifdef TASKSET p->next->cores = NULL; #endif @@ -609,7 +803,7 @@ static struct Job *newjobptr() { result->user_ms = 0.0; result->system_ms = 0.0; result->real_ms = 0.0; - + */ return p->next; } @@ -1067,6 +1261,9 @@ void job_finished(const struct Result *result, int jobid) { last_finished_jobid = p->jobid; notify_errorlevel(p); pinfo_set_end_time(&p->info); + if (result->real_ms == 0) { + p->info.start_time = p->info.enqueue_time = p->info.end_time; + } if (p->result.died_by_signal) pinfo_addinfo(&p->info, 100, "Exit status: killed by signal %i\n", @@ -1396,7 +1593,7 @@ void s_job_info(int s, int jobid) { t = pinfo_time_run(&p->info); fd_nprintf(s, 100, "End time: %s", ctime(&p->info.end_time.tv_sec)); } - char *unit = time_rep(&t); + const char *unit = time_rep(&t); fd_nprintf(s, 100, "Time running: %.4f %s\n", t, unit); // fd_nprintf(s, 100, "\n"); @@ -1663,6 +1860,7 @@ int s_remove_job(int s, int *jobid, int client_tsUID) { *jobid = p->jobid; delete_DB(p->jobid, "Jobs"); /* Tricks for the check_notify_list */ + printf("end job [%d] by remove cmd", p->jobid); p->state = FINISHED; p->result.errorlevel = -1; notify_errorlevel(p); diff --git a/list.c b/list.c index b1572b0..8a974db 100644 --- a/list.c +++ b/list.c @@ -83,7 +83,7 @@ char *joblist_headers() { line = malloc(256); snprintf(line, 256, - "%-4s %-7s %-7s %-6s %-10s %6s %-20s %s [run=%i/%i %.2f%%] Core: %-3d %s\n", + "%-4s %-7s %-7s %-6s %-10s %6s %-20s %s [run=%i/%i %.2f%%] Used Proc: %-3d %s\n", "ID", "State", "Proc.", "User", "Label", "Time", "Command", "Log", busy_slots, max_slots, 100.0 * busy_slots / max_slots, core_usage, extra); return line; @@ -120,7 +120,7 @@ static char *print_noresult(const struct Job *p) { int maxlen; char *line; /* 20 chars should suffice for a string like "[int,int,..]&& " */ - char dependstr[20] = ""; + char dependstr[1024] = "[]"; int cmd_len; jobstate = jstate2string(p->state); @@ -158,13 +158,13 @@ static char *print_noresult(const struct Job *p) { pos += snprintf(&dependstr[pos], sizeof(dependstr), ",%i", p->depend_on[i]); } - pos += snprintf(&dependstr[pos], sizeof(dependstr), "]&& "); + pos += snprintf(&dependstr[pos], sizeof(dependstr), "]"); } struct timeval starttv = p->info.start_time; struct timeval endtv; float real_ms; - char *unit; + const char *unit; if (p->state == QUEUED || p->pid == 0) { real_ms = 0; unit = " "; @@ -205,13 +205,13 @@ static char *print_result(const struct Job *p) { char *line; const char *output_filename; /* 20 chars should suffice for a string like "[int,int,..]&& " */ - char dependstr[20] = ""; + char dependstr[1024] = "[]&&"; float real_ms = p->result.real_ms; if (real_ms == 0.0) { real_ms = p->info.end_time.tv_sec - p->info.start_time.tv_sec; real_ms += 1e-6 * (p->info.end_time.tv_usec - p->info.start_time.tv_usec); } - char *unit = time_rep(&real_ms); + const char *unit = time_rep(&real_ms); int cmd_len; jobstate = jstate2string(p->state); @@ -274,7 +274,7 @@ static char *plainprint_noresult(const struct Job *p) { int maxlen; char *line; /* 20 chars should suffice for a string like "[int,int,..]&& " */ - char dependstr[20] = ""; + char dependstr[256] = "[]&&"; jobstate = jstate2string(p->state); output_filename = ofilename_shown(p); @@ -282,8 +282,11 @@ static char *plainprint_noresult(const struct Job *p) { maxlen = 4 + 1 + 10 + 1 + 20 + 1 + 8 + 1 + 25 + 1 + strlen(p->command) + 20 + strlen(uname) + 2; /* 20 is the margin for errors */ - if (p->label) - maxlen += 3 + strlen(p->label); + char* label = "(..)"; + if (p->label) { + label = p->label; + } + maxlen += 3 + strlen(label); if (p->depend_on_size) { maxlen += sizeof(dependstr); @@ -307,14 +310,21 @@ static char *plainprint_noresult(const struct Job *p) { line = (char *)malloc(maxlen); if (line == NULL) error("Malloc for %i failed.\n", maxlen); - - if (p->label) - snprintf(line, maxlen, "%i\t%s\t%s\t%s\t%s\t%s\t[%s]\t%s\n", p->jobid, - jobstate, output_filename, "", "", dependstr, p->label, - p->command + p->command_strip); - else - snprintf(line, maxlen, "%i\t%s\t%s\t%s\t%s\t%s\t\t%s\n", p->jobid, jobstate, - output_filename, "", "", dependstr, p->command + p->command_strip); + + float real_ms = 0; + const char* unit = "sx"; + if (p->state == RUNNING) { + struct timeval starttv = p->info.start_time; + struct timeval endtv; + gettimeofday(&endtv, NULL); + real_ms = endtv.tv_sec - starttv.tv_sec + + ((float)(endtv.tv_usec - starttv.tv_usec) / 1000000.); + unit = time_rep(&real_ms); + } + snprintf(line, maxlen, "%i\t%s\t%d\t%s\t%s\t%i\t%.2f%s\t%s\t%s\t%s\n", + p->jobid, jobstate, p->num_slots, user_name[p->ts_UID], label, + p->result.errorlevel, real_ms, unit, p->command + p->command_strip, + dependstr, output_filename); return line; } @@ -325,18 +335,27 @@ static char *plainprint_result(const struct Job *p) { char *line; const char *output_filename; /* 20 chars should suffice for a string like "[int,int,..]&& " */ - char dependstr[20] = ""; + char dependstr[256] = "[]"; float real_ms = p->result.real_ms; - char *unit = time_rep(&real_ms); + if (real_ms == 0.0) { + real_ms = p->info.end_time.tv_sec - p->info.start_time.tv_sec; + real_ms += 1e-6 * (p->info.end_time.tv_usec - p->info.start_time.tv_usec); + } + + const char *unit = time_rep(&real_ms); jobstate = jstate2string(p->state); output_filename = ofilename_shown(p); maxlen = 4 + 1 + 10 + 1 + 20 + 1 + 8 + 1 + 25 + 1 + strlen(p->command) + - 20; /* 20 is the margin for errors */ + 30 + strlen(user_name[p->ts_UID]); /* 30 is the margin for errors */ - if (p->label) - maxlen += 3 + strlen(p->label); + + char* label = "(..)"; + if (p->label) { + label = p->label; + } + maxlen += 3 + strlen(label); if (p->depend_on_size) { maxlen += sizeof(dependstr); @@ -354,22 +373,18 @@ static char *plainprint_result(const struct Job *p) { pos += snprintf(&dependstr[pos], sizeof(dependstr), ",%i", p->depend_on[i]); } - pos += snprintf(&dependstr[pos], sizeof(dependstr), "]&& "); + pos += snprintf(&dependstr[pos], sizeof(dependstr), "]"); } line = (char *)malloc(maxlen); if (line == NULL) error("Malloc for %i failed.\n", maxlen); - if (p->label) - snprintf(line, maxlen, "%i\t%s\t%s\t%i\t%.2f\t%s\t%s\t[%s]\t%s\n", p->jobid, - jobstate, output_filename, p->result.errorlevel, real_ms, unit, - dependstr, p->label, p->command + p->command_strip); - else - snprintf(line, maxlen, "%i\t%s\t%s\t%i\t%.2f\t%s\t%s\t\t%s\n", p->jobid, - jobstate, output_filename, p->result.errorlevel, real_ms, unit, - dependstr, p->command + p->command_strip); + snprintf(line, maxlen, "%i\t%s\t%d\t%s\t%s\t%i\t%.2f%s\t%s\t%s\t%s\n", + p->jobid, jobstate, p->num_slots, user_name[p->ts_UID], label, + p->result.errorlevel, real_ms, unit, p->command + p->command_strip, + dependstr, output_filename); return line; } @@ -410,7 +425,7 @@ char *joblistdump_torun(const struct Job *p) { return line; } -char *time_rep(float *t) { +const char *time_rep(float *t) { float time_in_sec = *t; char *unit = "s"; if (time_in_sec > 250) { diff --git a/main.c b/main.c index 9353397..5c22717 100644 --- a/main.c +++ b/main.c @@ -53,7 +53,6 @@ static void init_version() { static void default_command_line() { command_line.request = c_LIST; - command_line.plain_list = 0; command_line.need_server = 0; command_line.store_output = 1; command_line.should_go_background = 1; @@ -73,6 +72,7 @@ static void default_command_line() { command_line.taskpid = 0; command_line.start_time = 0; command_line.jobid = 0; + command_line.list_format = DEFAULT; } struct Msg default_msg() { @@ -126,16 +126,16 @@ int strtok_int(char *str, char *delim, int *ids) { } static struct option longOptions[] = { - {"get_label", required_argument, NULL, 'a'}, - {"count_running", no_argument, NULL, 'R'}, - {"help", no_argument, NULL, 0}, - {"last_queue_id", no_argument, NULL, 'q'}, - {"full_cmd", required_argument, NULL, 'F'}, - {"plain", no_argument, NULL, 0}, - {"get_logdir", no_argument, NULL, 0}, - {"set_logdir", required_argument, NULL, 0}, - {"getenv", required_argument, NULL, 0}, - {"setenv", required_argument, NULL, 0}, + {"get_label", required_argument, NULL, 'a'}, + {"count_running", no_argument, NULL, 'R'}, + {"help", no_argument, NULL, 0}, + {"serialize", required_argument, NULL, 'M'}, + {"last_queue_id", no_argument, NULL, 'q'}, + {"full_cmd", required_argument, NULL, 'F'}, + {"get_logdir", no_argument, NULL, 0}, + {"set_logdir", required_argument, NULL, 0}, + {"getenv", required_argument, NULL, 0}, + {"setenv", required_argument, NULL, 0}, {"unsetenv", required_argument, NULL, 0}, {"stop", optional_argument, NULL, 0}, {"cont", optional_argument, NULL, 0}, @@ -157,7 +157,7 @@ void parse_opts(int argc, char **argv) { /* Parse options */ while (1) { c = getopt_long(argc, argv, - ":AXRTVhKzClnfmBE:a:F:t:c:o:p:w:k:r:u:s:U:qi:N:J:L:dS:D:W:O:", + ":AXRTVhKzClnfmBE:a:F:t:c:o:p:w:k:r:u:s:U:qi:N:J:L:dS:D:W:O:M:", longOptions, &optionIdx); if (c == -1) @@ -205,9 +205,6 @@ void parse_opts(int argc, char **argv) { } else if (strcmp(longOptions[optionIdx].name, "unsetenv") == 0) { command_line.request = c_UNSET_ENV; command_line.label = optarg; /* reuse this var */ - } else if (strcmp(longOptions[optionIdx].name, "plain") == 0) { - command_line.request = c_LIST; - command_line.plain_list = 1; } else if (strcmp(longOptions[optionIdx].name, "full_cmd") == 0) { command_line.request = c_SHOW_CMD; if (optarg != NULL) { @@ -397,6 +394,20 @@ void parse_opts(int argc, char **argv) { case 'R': command_line.request = c_COUNT_RUNNING; break; + case 'M': + command_line.request = c_LIST; + + if (strcmp(optarg, "default") == 0) + command_line.list_format = DEFAULT; + else if (strcmp(optarg, "json") == 0) + command_line.list_format = JSON; + else if (strcmp(optarg, "tab") == 0) + command_line.list_format = TAB; + else { + fprintf(stderr, "Invalid argument for option M: %s.\n", optarg); + exit(-1); + } + break; case ':': switch (optopt) { case 't': @@ -454,6 +465,10 @@ void parse_opts(int argc, char **argv) { command_line.request = c_SHOW_CMD; command_line.jobid = -1; break; + case 'M': + command_line.request = c_LIST; + command_line.list_format = DEFAULT; + break; default: fprintf(stderr, "Option %c missing argument.\n", optopt); exit(-1); @@ -574,8 +589,8 @@ static void print_help(const char *cmd) { " --get_logdir get the path containing log files.\n"); printf( " --set_logdir [path] set the path containing log files.\n"); - printf(" --plain list jobs in plain tab-separated " - "texts.\n"); + printf( + " --serialize [format] || -M [format] serialize the job list to the specified format. Choices: {default, json, tab}.\n"); printf(" --daemon Run the server as daemon by Root " "only.\n"); printf(" --pause [jobid] hold on a task.\n"); @@ -667,6 +682,7 @@ static void get_terminal_width() { term_width = ws.ws_col; } +// TODO add the benchmark for the performance. int main(int argc, char **argv) { int errorlevel = 0; jobsort_flag = 0; diff --git a/main.h b/main.h index 63c214a..00a06a6 100644 --- a/main.h +++ b/main.h @@ -64,6 +64,12 @@ enum MsgTypes { UNSET_ENV }; +enum ListFormat { + DEFAULT, + JSON, + TAB +}; + enum Request { c_QUEUE, c_TAIL, @@ -108,7 +114,6 @@ enum Request { struct CommandLine { enum Request request; - int plain_list; int need_server; int store_output; int stderr_apart; @@ -135,6 +140,7 @@ struct CommandLine { int taskpid; /* to restore task by pid */ int require_elevel; /* whether requires error level of dependencies or not */ long start_time; + enum ListFormat list_format; }; enum ProcessType { CLIENT, SERVER }; @@ -194,8 +200,8 @@ struct Msg { int count_running; char *label; struct { - int plain_list; int term_width; + enum ListFormat list_format; } list; } u; }; @@ -322,8 +328,8 @@ void c_set_env(); void c_unset_env(); /* jobs.c */ -void s_list(int s, int ts_UID); -void s_list_all(int s); +void s_list(int s, int ts_UID, enum ListFormat listFormat); +void s_list_all(int s, enum ListFormat listFormat); void s_list_plain(int s); @@ -480,7 +486,7 @@ char *joblistdump_torun(const struct Job *p); char *joblistdump_headers(); -char *time_rep(float *t); +const char *time_rep(float *t); /* print.c */ int fd_nprintf(int fd, int maxsize, const char *fmt, ...); diff --git a/server.c b/server.c index 734f91a..c1b274f 100644 --- a/server.c +++ b/server.c @@ -451,12 +451,9 @@ static void clean_after_client_disappeared(int socket, int index) { static void s_remove_all_queues(int ts_UID) { int i = 0; - struct Job* p; while(i < nconnections - 1) { if (ts_UID == 0 || client_cs[i].ts_UID == ts_UID) { - p = findjob(client_cs[i].jobid); - if (p->state != RUNNING) { - pinfo_set_start_time(&p->info); + if (job_is_running(client_cs[i].jobid) != 1) { clean_after_client_disappeared(client_cs[i].socket, i); } else { i++; // To next one @@ -477,7 +474,7 @@ static enum Break client_read(int index) { /* Read the message */ res = recv_msg(s, &m); if (res == -1) { - warning("client recv failed"); + warning("client recv failed"); clean_after_client_disappeared(s, index); return NOBREAK; } else if (res == 0) { @@ -606,22 +603,16 @@ static enum Break client_read(int index) { */ case LIST: term_width = m.u.list.term_width; - if (m.u.list.plain_list) - s_list_plain(s); - else - s_list(s, ts_UID); - s_user_status(s, ts_UID); + s_list(s, ts_UID, m.u.list.list_format); // list ts_UID user + /* We must actively close, meaning End of Lines */ close(s); remove_connection(index); break; case LIST_ALL: term_width = m.u.list.term_width; - if (m.u.list.plain_list) - s_list_plain(s); - else - s_list_all(s); - s_user_status_all(s); + s_list(s, 0, m.u.list.list_format); // list all + /* We must actively close, meaning End of Lines */ close(s); remove_connection(index); diff --git a/sqlite.c b/sqlite.c index 4b2e362..240aaa8 100644 --- a/sqlite.c +++ b/sqlite.c @@ -365,7 +365,7 @@ struct Job* read_DB(int jobid, const char* table) { // 从查询结果中读取数据 sqlite3_stmt *stmt; - int size, rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (rc != SQLITE_OK) { fprintf(stderr,"[read_DB1] SQL error: %s\n", sqlite3_errmsg(db)); return NULL; // 返回-1表示查询失败 @@ -394,13 +394,21 @@ struct Job* read_DB(int jobid, const char* table) { // job->depend_on_size = sqlite3_column_bytes(stmt, 9); // job->notify_errorlevel_to_size = sqlite3_column_bytes(stmt, 11); - strcpy(sql, (const char*) sqlite3_column_text(stmt, 8)); - job->depend_on = chars_to_ints(&size, sql, ","); - job->depend_on_size = size; - strcpy(sql, (const char*) sqlite3_column_text(stmt, 8)); - job->notify_errorlevel_to = chars_to_ints(&size, sql, ","); - job->notify_errorlevel_to_size = size; - + job->depend_on_size = sqlite3_column_int(stmt, 9); + if (job->depend_on_size == 0) { + job->depend_on = NULL; + } else { + strcpy(sql, (const char*) sqlite3_column_text(stmt, 8)); + job->depend_on = chars_to_ints(&job->depend_on_size, sql, ","); + } + + job->notify_errorlevel_to_size = sqlite3_column_int(stmt, 11); + if (job->notify_errorlevel_to_size == 0) { + job->notify_errorlevel_to = NULL; + } else { + strcpy(sql, (const char*) sqlite3_column_text(stmt, 10)); + job->notify_errorlevel_to = chars_to_ints(&job->notify_errorlevel_to_size, sql, ","); + } job->dependency_errorlevel = sqlite3_column_int(stmt, 12); diff --git a/taskset.c b/taskset.c index 87c561c..9fcd5c3 100644 --- a/taskset.c +++ b/taskset.c @@ -80,7 +80,7 @@ int set_task_cores(struct Job* p, const char* extra) { char* cmd = (char*) malloc(sizeof(char) * size); sprintf(cmd, "taskset -cp %s ", core_str); if (extra == NULL) { - printf("[CMD] %s %d\n", cmd, p->pid); + ; // printf("[CMD] %s %d\n", cmd, p->pid); } else { printf("[CMD] %s %d; %s %d\n", cmd, p->pid, extra, p->pid); } diff --git a/ttail.c b/ttail.c deleted file mode 100644 index e69de29..0000000 diff --git a/user.c b/user.c index 13e4e07..74653a6 100644 --- a/user.c +++ b/user.c @@ -122,7 +122,10 @@ void check_running_task(int pid) { } - +/* + ts_UID from user_number is 1-indexing + The minimal ts_UID for a valid user is 1; +*/ void write_logfile(const struct Job *p) { // char buf[1024] = ""; FILE *f = fopen(logfile_path, "a"); From 4a48b7930850fd7d265a4b8fe545d842c61f3ddd Mon Sep 17 00:00:00 2001 From: kylin Date: Mon, 27 Mar 2023 15:45:44 +0800 Subject: [PATCH 56/91] add relink feature to restore task-spooler service --- execute.c | 2 +- info.c | 5 ++ jobs.c | 145 ++++++++++++++++++++++++++++++------------------------ main.h | 24 ++++++--- print.c | 16 ++++++ relink.py | 4 +- server.c | 20 ++++---- sqlite.c | 2 +- user.c | 4 +- 9 files changed, 136 insertions(+), 86 deletions(-) diff --git a/execute.c b/execute.c index c8834a6..e85105a 100644 --- a/execute.c +++ b/execute.c @@ -71,7 +71,7 @@ static void run_relink(int pid, struct Result *result) { signals_child_pid = pid; unblock_sigint_and_install_handler(); // printf("runjob_ok %s\n", ofname); - c_send_runjob_ok(ofname, -1); + c_send_runjob_ok(ofname, pid); wait_for_pid(pid); diff --git a/info.c b/info.c index 50c3977..b70fb1c 100644 --- a/info.c +++ b/info.c @@ -100,6 +100,11 @@ void pinfo_set_enqueue_time(struct Procinfo *p) p->end_time.tv_usec = 0; } +void pinfo_set_start_time_check(struct Procinfo *info) { + if (info->start_time.tv_sec != 0 && info->start_time.tv_usec != 0) { + pinfo_set_start_time(info); + } +} void pinfo_set_start_time(struct Procinfo *p) { gettimeofday(&p->start_time, 0); diff --git a/jobs.c b/jobs.c index 2f91806..41418b8 100644 --- a/jobs.c +++ b/jobs.c @@ -310,15 +310,15 @@ struct Job *findjob(int jobid) { } -static int jobid_from_pid(int pid) { - // if (pid == 0) return 0; +static struct Job* job_by_pid(int pid) { + if (pid == 0) return NULL; struct Job *p = &firstjob; while (p->next != NULL) { p = p->next; - if (p->pid == pid) return p->jobid; + if (p->pid == pid) return p; } - return -1; + return NULL; } // return 1 for running, other is dead @@ -329,12 +329,12 @@ int s_check_running_pid(int pid) { return strlen(filename) != 0; } -// if any error return 0; -int s_check_relink(int s, struct Msg *m, int ts_UID) { - int pid = m->u.newjob.taskpid; - int jobid = jobid_from_pid(pid); - if (jobid != -1) { - sprintf(buff, " Error: PID [%i] has already in job queue as Jobid: %i\n", pid, jobid); +// if any error return non-0; +int s_check_relink(int s, int pid, int ts_UID) { + struct Job* p = job_by_pid(pid); + if (p != NULL && (p->state != DELINK && p->state != WAIT)) { + sprintf(buff, " Error: PID [%i] is already in job as Jobid: %i [%s]\n", + pid, p->jobid, jstate2string(p->state)); send_list_line(s, buff); return -1; } @@ -351,9 +351,9 @@ int s_check_relink(int s, struct Msg *m, int ts_UID) { int job_tsUID = get_tsUID(t_stat.st_uid); if (ts_UID == 0) { - return job_tsUID; + ; } else if (ts_UID == job_tsUID) { - return job_tsUID; + ; } else { sprintf(buff, " Error: PID [%i] is owned by [%d] `%s` not the user [%d] `%s`\n", pid, user_UID[job_tsUID], user_name[job_tsUID], @@ -361,6 +361,7 @@ int s_check_relink(int s, struct Msg *m, int ts_UID) { send_list_line(s, buff); return -1; } + return job_tsUID; } /* @@ -590,6 +591,9 @@ const char *jstate2string(enum Jobstate s) { case WAIT: jobstate = "wait "; break; + case DELINK: + jobstate = "delink "; + break; } return jobstate; } @@ -776,7 +780,7 @@ static struct Job *newjobptr() { while (p->next != 0) p = p->next; - p->next = (struct Job *)calloc(sizeof(*p), sizeof(char)); + p->next = (struct Job *)calloc(sizeof(struct Job), sizeof(char)); /* p->next->next = 0; p->next->output_filename = 0; @@ -838,19 +842,26 @@ static int find_last_stored_jobid_finished() { } /* Returns job id or -1 on error */ -int s_newjob(int s, struct Msg *m, int ts_UID, int socket) { +int s_newjob(int s, struct Msg *m, int ts_UID) { struct Job *p; int res; - int waitjob_flag = 0; + int waitjob_flag = 0; // 0 for newjob, 1 for WAIT and 2 for DELINK if (m->jobid != 0) { p = findjob(m->jobid); - if (p != NULL && p->state == WAIT) { - jobDB_wait_num--; - waitjob_flag = 1; - p->state = QUEUED; - } else { - return -1; + // if p == NULL => Manual Relink + if (p != NULL) { + // WAIT for restore queued tasks + if (p->state == DELINK) { + waitjob_flag = 2; + p->state = RELINK; + } else if (p->state == WAIT) { + // jobDB_wait_num--; + waitjob_flag = 1; + p->state = QUEUED; + } else { + return -1; + } } } @@ -972,8 +983,10 @@ int s_newjob(int s, struct Msg *m, int ts_UID, int socket) { if (p->depend_on_size == 0) p->depend_on = 0; - pinfo_init(&p->info); - pinfo_set_enqueue_time(&p->info); + if (waitjob_flag != 2) { + pinfo_init(&p->info); + pinfo_set_enqueue_time(&p->info); + } /* load the command */ @@ -1035,23 +1048,26 @@ int s_newjob(int s, struct Msg *m, int ts_UID, int socket) { free(ptr); } - /* for relink running task */ - if (m->u.newjob.taskpid != 0) { - p->pid = m->u.newjob.taskpid; - struct Procinfo* pinfo = &(p->info); - pinfo->start_time.tv_sec = 0; - int num_slots = p->num_slots, id = p->ts_UID; - busy_slots += num_slots; - user_busy[id] += num_slots; - user_jobs[id]++; - p->state = RELINK; - p->info.start_time.tv_sec = m->u.newjob.start_time; - p->info.start_time.tv_usec = 0; + if (waitjob_flag == 0) { + // real new job + if (m->u.newjob.taskpid == 0) { + insert_DB(p, "Jobs"); + } else { + /* for manually relink running task */ + p->pid = m->u.newjob.taskpid; + struct Procinfo* pinfo = &(p->info); + pinfo->start_time.tv_sec = 0; + int num_slots = p->num_slots, id = p->ts_UID; + busy_slots += num_slots; + user_busy[id] += num_slots; + user_jobs[id]++; + p->state = RELINK; + p->info.start_time.tv_sec = m->u.newjob.start_time; + p->info.start_time.tv_usec = 0; + insert_or_replace_DB(p, "Jobs"); + } + } // waitjob_flag == 0 - } - if(waitjob_flag == 0) { - insert_DB(p, "Jobs"); - } set_jobids_DB(jobids); return p->jobid; } @@ -1355,14 +1371,16 @@ static int fork_cmd(int UID, const char* path, const char* cmd) { } static void s_add_job(struct Job* j, struct Job** p) { - if (j->state == RUNNING || j->state == HOLDING_CLIENT || j->state == RELINK) { + // if (j->state == RUNNING || j->state == HOLDING_CLIENT || j->state == RELINK) { + if (j->state == RUNNING) { if (j->pid > 0 && s_check_running_pid(j->pid) == 1) { printf("add job %d\n", j->jobid); int ts_UID = j->ts_UID; user_jobs[ts_UID]++; - if (j->state == RUNNING && check_ifsleep(j->pid) == 0) { + // if (j->state == RUNNING && check_ifsleep(j->pid) == 0) { + if (check_ifsleep(j->pid) == 0) { int slots = j->num_slots; user_busy[ts_UID] += slots; busy_slots += slots; @@ -1370,40 +1388,43 @@ static void s_add_job(struct Job* j, struct Job** p) { set_task_cores(j, NULL); #endif } + j->state = DELINK; + - jobDB_Jobs[jobDB_num] = j; - jobDB_num++; + // jobDB_Jobs[jobDB_num] = j; + // jobDB_num++; (*p)->next = j; (*p) = j; + char c[64]; + sprintf(c, " --relink %d -J %d ", j->pid, j->jobid); + char* str = insert_chars(j->command_strip, j->command, c); + printf("fork str = %s\n", str); + fork_cmd(user_UID[j->ts_UID], j->work_dir, str); + printf("end of fork\n"); + jobids = jobids > j->jobid ? jobids : j->jobid + 1; j = NULL; + } else { + delete_DB(j->jobid, "JObs"); } } else if (j->state == QUEUED) { printf("add the queue job %d\n", j->jobid); j->state = WAIT; - jobDB_wait_num++; + // jobDB_wait_num++; (*p)->next = j; (*p) = j; - char c[32]; //创建一个存储数字的字符串 - sprintf(c, "-J %d ", j->jobid); //将数字转换为字符串 - int len = strlen(j->command) + strlen(c) + 1; //计算新字符串的长度 - char *str = (char *)calloc(0, len * sizeof(char)); //用malloc函数分配内存 - if (str == NULL) //判断是否分配成功 - { - printf("Memory allocation failed.\n"); - return; - } - strncpy(str, j->command, j->command_strip); //将s数组的前t个字符复制到str中 - strcat(str, c); //将数字字符串连接到str后面 - strcat(str, (j->command + j->command_strip)); //将s剩余的字符串连接到str后面 + char c[32]; + sprintf(c, " -J %d ", j->jobid); + char* str = insert_chars(j->command_strip, j->command, c); + fork_cmd(user_UID[j->ts_UID], j->work_dir, str); jobids = jobids > j->jobid ? jobids : j->jobid + 1; j = NULL; - + free(str); /* printf("add job %d; CMD = %s\n", j->jobid, j->command); jobDB_Jobs[jobDB_num] = j; @@ -1423,7 +1444,7 @@ void s_read_sqlite() { p = &firstjob; num_jobs = read_jobid_DB(&(jobs_DB), "Jobs"); // printf("read from jobs %d\n", num_jobs); - jobDB_Jobs = (struct Job**)malloc(sizeof(struct Job*) * num_jobs); + // jobDB_Jobs = (struct Job**)malloc(sizeof(struct Job*) * num_jobs); printf("Jobs:\n"); for (int i = 0; i < num_jobs; i++) { job = read_DB(jobs_DB[i], "Jobs"); @@ -1477,6 +1498,7 @@ void s_clear_finished(int ts_UID) { other_user_job->next = NULL; } +// run the jobs void s_process_runjob_ok(int jobid, char *oname, int pid) { struct Job *p; p = findjob(jobid); @@ -1487,7 +1509,7 @@ void s_process_runjob_ok(int jobid, char *oname, int pid) { p->pid = pid; p->output_filename = oname; - pinfo_set_start_time(&p->info); + pinfo_set_start_time_check(&p->info); insert_or_replace_DB(p, "Jobs"); #ifdef TASKSET @@ -1923,14 +1945,12 @@ static struct Job *get_job(int jobid) { return 0; } -int s_check_locker(int s, int ts_UID) { +int s_check_locker(int ts_UID) { int dt = time(NULL) - locker_time; int res; - if (user_locker != 0 && dt > 30) { user_locker = -1; } - if (user_locker == -1) { res = 0; } else { @@ -1940,7 +1960,6 @@ int s_check_locker(int s, int ts_UID) { res = 1; } } - return res; } diff --git a/main.h b/main.h index 00a06a6..097bb7c 100644 --- a/main.h +++ b/main.h @@ -153,8 +153,16 @@ extern int term_width; struct Msg; -enum Jobstate { QUEUED, RUNNING, FINISHED, SKIPPED, - HOLDING_CLIENT, RELINK, WAIT}; +enum Jobstate { + QUEUED, + RUNNING, + FINISHED, + SKIPPED, + HOLDING_CLIENT, + RELINK, + WAIT, + DELINK, + }; struct Msg { enum MsgTypes type; @@ -333,7 +341,7 @@ void s_list_all(int s, enum ListFormat listFormat); void s_list_plain(int s); -int s_newjob(int s, struct Msg *m, int ts_UID, int socket); +int s_newjob(int s, struct Msg *m, int ts_UID); void s_delete_job(int jobid); @@ -504,6 +512,7 @@ int pinfo_size(const struct Procinfo *p); void pinfo_set_enqueue_time(struct Procinfo *p); void pinfo_set_start_time(struct Procinfo *p); +void pinfo_set_start_time_check(struct Procinfo *info); void pinfo_set_end_time(struct Procinfo *p); @@ -559,10 +568,10 @@ void s_pause_job(int s, int jobid, int uid); void s_rerun_job(int s, int jobid, int uid); void s_lock_server(int s, int uid); void s_unlock_server(int s, int uid); -int s_check_locker(int s, int uid); +int s_check_locker(int uid); void s_set_jobids(int i); void s_sort_jobs(); -int s_check_relink(int s, struct Msg *m, int ts_UID); +int s_check_relink(int s, int pid, int ts_UID); void s_read_sqlite(); int s_check_running_pid(int pid); struct Job *findjob(int jobid); @@ -590,12 +599,13 @@ void movetop_DB(int jobid); void swap_DB(int, int); void set_jobids_DB(int value); int get_jobids_DB(); -int jobDB_num, jobDB_wait_num; -struct Job** jobDB_Jobs; +// int jobDB_num, jobDB_wait_num; +// struct Job** jobDB_Jobs; /* print.c */ char* ints_to_chars(int n, int *array, const char *delim); int* chars_to_ints(int *size, char* str, const char* delim); +char* insert_chars(int pos, const char* input, const char* c); /* taskset.c */ void init_taskset(); diff --git a/print.c b/print.c index a15c19c..6505b63 100644 --- a/print.c +++ b/print.c @@ -50,6 +50,7 @@ int fd_nprintf(int fd, int maxsize, const char *fmt, ...) { char *ints_to_chars(int n, int *array, const char *delim) { int size = n * 12 + n * strlen(delim) + 1; char *tmp = (char*) malloc(size * sizeof(char)); + tmp[0] = '\0'; int j = 0; for (int i = 0; i < n; i++) { j += sprintf(tmp + j, "%d", array[i]); @@ -70,6 +71,7 @@ int* chars_to_ints(int *size, char* str, const char* delim) { } count++; int *result = malloc(count * sizeof(int)); + result[0] = '\0'; char *token = strtok(str, delim); int index = 0; while (token != NULL) { @@ -79,3 +81,17 @@ int* chars_to_ints(int *size, char* str, const char* delim) { *size = count; return result; } + +char* insert_chars(int pos, const char* input, const char* c) { + int len = strlen(input) + strlen(c) + 1; + char *str = (char *)calloc(len, sizeof(char)); //用malloc函数分配内存 + if (str == NULL) //判断是否分配成功 + { + error("Memory allocation failed.\n"); + return NULL; + } + strncpy(str, input, pos); //将s数组的前t个字符复制到str中 + strcat(str, c); //将数字字符串连接到str后面 + strcat(str, input + pos); //将s剩余的字符串连接到str后面 + return str; +} \ No newline at end of file diff --git a/relink.py b/relink.py index 8a30e3f..bb8cbd1 100755 --- a/relink.py +++ b/relink.py @@ -57,10 +57,10 @@ def parse(s): for i in tasks[:]: if i[0] == "..": - CMD = '{} --pid {} -N {} "{}"'.format(ts_CMD, *i[1:]) + CMD = '{} --relink {} -N {} "{}"'.format(ts_CMD, *i[1:]) # CMD = 'ts --pid {} -N {} --stime {:} "{}"'.format(*i[1:]) else: - CMD = '{} -L {} --pid {} -N {} "{}"'.format(ts_CMD, *i) + CMD = '{} -L {} --relink {} -N {} "{}"'.format(ts_CMD, *i) # CMD = 'ts -L {} --pid {} -N {} --stime {:} "{}"'.format(*i) print(CMD) os.system(CMD) diff --git a/server.c b/server.c index c1b274f..fea7d29 100644 --- a/server.c +++ b/server.c @@ -170,6 +170,7 @@ static int get_max_descriptors() { return max; } +/* static void check_jobDB() { struct Job* p; for (int i = 0; i < jobDB_num; i++) { @@ -195,7 +196,7 @@ static void check_jobDB() { } } } - +*/ /* int s_add_connection(int jobid, int socket, int hasjob, int ts_UID) { @@ -261,8 +262,8 @@ void server_main(int notify_fd, char *_path) { user_jobs[i] = 0; user_queue[i] = 0; } - jobDB_num = jobDB_wait_num = 0; - jobDB_Jobs = NULL; + // jobDB_num = jobDB_wait_num = 0; + // jobDB_Jobs = NULL; init_taskset(); set_server_logfile(); // int jobid = read_first_jobid_from_logfile(logfile_path); @@ -371,9 +372,7 @@ static void server_loop(int ls) { */ } /* This will return firstjob->jobid or -1 */ - if (jobDB_num > 0) { - check_jobDB(); - } + newjob = next_run_job(); if (newjob != -1) { @@ -552,9 +551,9 @@ static enum Break client_read(int index) { case NEWJOB: if (m.u.newjob.taskpid != 0) { // check if taskpid isnot in queue and from a valid user. - ts_UID = s_check_relink(s, &m, ts_UID); + ts_UID = s_check_relink(s, m.u.newjob.taskpid, ts_UID); } else { - if (s_check_locker(s, ts_UID) == 1) { break; } + if (s_check_locker(ts_UID) == 1) { break; } } if (ts_UID < 0 || ts_UID > USER_MAX) { @@ -564,9 +563,10 @@ static enum Break client_read(int index) { // close(s); break; } - - client_cs[index].jobid = s_newjob(s, &m, ts_UID, s); + + client_cs[index].jobid = s_newjob(s, &m, ts_UID); client_cs[index].hasjob = 1; + if (client_cs[index].jobid == -1) { s_newjob_nok(index); client_cs[index].hasjob = 0; diff --git a/sqlite.c b/sqlite.c index 240aaa8..950db0f 100644 --- a/sqlite.c +++ b/sqlite.c @@ -65,7 +65,7 @@ static int get_order_id(int jobid) { } void close_sqlite() { - free(jobDB_Jobs); + // free(jobDB_Jobs); sqlite3_close(db); } diff --git a/user.c b/user.c index 74653a6..b7eb08e 100644 --- a/user.c +++ b/user.c @@ -109,9 +109,9 @@ void check_running_task(int pid) { char* f = (char*) malloc(namesize); strncpy(f, filename, namesize); if (strlen(f) == 0) { - error("PID: %d is dead", pid); + error("Client: PID[%d] is dead", pid); } else { - printf("tast stdout > %s\n", filename); + printf("Client: PID[%d] => %s\n", pid, filename); } struct stat t_stat; snprintf(filename, 256, "/proc/%d/stat", pid); From 800914962be2e49addf757679ea04f67f7edb2a8 Mon Sep 17 00:00:00 2001 From: kylin Date: Mon, 27 Mar 2023 17:46:27 +0800 Subject: [PATCH 57/91] add default_path.h --- default_path.h | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 default_path.h diff --git a/default_path.h b/default_path.h new file mode 100644 index 0000000..83a1426 --- /dev/null +++ b/default_path.h @@ -0,0 +1,3 @@ +#define DEFAULT_USER_PATH "/home/kylin/task-spooler/user.txt" +#define DEFAULT_LOG_PATH "/home/kylin/task-spooler/log.txt" +#define DEFAULT_SQLITE_PATH "/home/kylin/task-spooler/task-spooler.db" From ccf03521e1304d21a1127363c4de6156182fa8bf Mon Sep 17 00:00:00 2001 From: kylin Date: Mon, 27 Mar 2023 17:52:54 +0800 Subject: [PATCH 58/91] add task-spooler.service --- task-spooler.service | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 task-spooler.service diff --git a/task-spooler.service b/task-spooler.service new file mode 100644 index 0000000..0806a35 --- /dev/null +++ b/task-spooler.service @@ -0,0 +1,14 @@ +[Unit] +Description=task-spooler +After=syslog.target + +[Service] +ExecStart=/usr/local/sbin/task-spooler --daemon +SuccessExitStatus=143 + +[Install] +WantedBy=multi-user.target + +# install +# 01 sudo systemctl enable /home/user/task-spooler.service +# 02 sudo systemctl start task-spooler.service From c0300689dfb99c8f7257718ff6294e4ec6fd7b51 Mon Sep 17 00:00:00 2001 From: kylin Date: Mon, 27 Mar 2023 18:41:55 +0800 Subject: [PATCH 59/91] fixed the bug on the start time and remove the popen function --- info.c | 2 +- jobs.c | 11 +++++------ main.c | 7 +++++-- main.h | 2 +- user.c | 31 +++++++++++++++++-------------- 5 files changed, 29 insertions(+), 24 deletions(-) diff --git a/info.c b/info.c index b70fb1c..cfb1beb 100644 --- a/info.c +++ b/info.c @@ -101,7 +101,7 @@ void pinfo_set_enqueue_time(struct Procinfo *p) } void pinfo_set_start_time_check(struct Procinfo *info) { - if (info->start_time.tv_sec != 0 && info->start_time.tv_usec != 0) { + if (info->start_time.tv_sec == 0 && info->start_time.tv_usec == 0) { pinfo_set_start_time(info); } } diff --git a/jobs.c b/jobs.c index 41418b8..c97eaaa 100644 --- a/jobs.c +++ b/jobs.c @@ -323,10 +323,10 @@ static struct Job* job_by_pid(int pid) { // return 1 for running, other is dead int s_check_running_pid(int pid) { - char cmd[256], filename[256] = ""; - snprintf(cmd, sizeof(cmd), "readlink -f /proc/%d/fd/1", pid); - linux_cmd(cmd, filename, sizeof(filename)); - return strlen(filename) != 0; + // char cmd[256], filename[256] = ""; + // snprintf(cmd, sizeof(cmd), "readlink -f /proc/%d/fd/1", pid); + // linux_cmd(cmd, filename, sizeof(filename)); + return kill(pid, 0) == 0; } // if any error return non-0; @@ -1399,9 +1399,8 @@ static void s_add_job(struct Job* j, struct Job** p) { char c[64]; sprintf(c, " --relink %d -J %d ", j->pid, j->jobid); char* str = insert_chars(j->command_strip, j->command, c); - printf("fork str = %s\n", str); + fork_cmd(user_UID[j->ts_UID], j->work_dir, str); - printf("end of fork\n"); jobids = jobids > j->jobid ? jobids : j->jobid + 1; j = NULL; diff --git a/main.c b/main.c index 5c22717..02f0108 100644 --- a/main.c +++ b/main.c @@ -145,6 +145,7 @@ static struct option longOptions[] = { {"unlock-ts", no_argument, NULL, 0}, {"daemon", no_argument, NULL, 0}, {"relink", required_argument, NULL, 0}, + {"jobid", required_argument, NULL, 'J'}, {"stime", required_argument, NULL, 0}, {"check_daemon", no_argument, NULL, 0}, {NULL, 0, NULL, 0}}; @@ -590,7 +591,7 @@ static void print_help(const char *cmd) { printf( " --set_logdir [path] set the path containing log files.\n"); printf( - " --serialize [format] || -M [format] serialize the job list to the specified format. Choices: {default, json, tab}.\n"); + " --serialize || -M [format] serialize the job list to the specified format. Choices: {default, json, tab}.\n"); printf(" --daemon Run the server as daemon by Root " "only.\n"); printf(" --pause [jobid] hold on a task.\n"); @@ -606,7 +607,9 @@ static void print_help(const char *cmd) { "paused tasks and lock the account. \n " " For root, to unlock all users or single [user].\n"); printf(" --relink [PID] Relink the running tasks by its [PID] from an expected failure.\n"); - printf(" --stime [start_time] Set the relinked task by starting time (Unix epoch).\n"); + printf( + " --job [joibid] || -J [joibid] set the new or relink job ID by jobid\n"); + // printf(" --stime [start_time] Set the relinked task by starting time (Unix epoch).\n"); printf("Actions:\n"); printf(" -A Show all users information\n"); printf( diff --git a/main.h b/main.h index 097bb7c..7070599 100644 --- a/main.h +++ b/main.h @@ -543,7 +543,7 @@ void debug_write(const char *str); const char *uid2user_name(int uid); int read_first_jobid_from_logfile(const char *path); void kill_pid(int ppid, const char *signal, const char* extra); -char* linux_cmd(char* CMD, char* out, int out_size); +// char* linux_cmd(char* CMD, char* out, int out_size); char **split_str(const char *str, int *size); void check_running_task(int pid); char *charArray_string(int num, char** array); diff --git a/user.c b/user.c index b7eb08e..9abf83d 100644 --- a/user.c +++ b/user.c @@ -62,26 +62,27 @@ char **split_str(const char *str0, int *size) { return result; //返回数组 } +/* char* linux_cmd(char* CMD, char* out, int out_size) { FILE *fp; - /* Open the command for reading. */ + /- Open the command for reading. -/ fp = popen(CMD, "r"); if (fp == NULL) { printf("Failed to run command: %s\n", CMD); exit(1); } - /* Read the output a line at a time - output it. */ + /- Read the output a line at a time - output it. -/ while (fgets(out, out_size, fp) != NULL) { ; // printf("%s", path); } char* end = memchr(out, '\n', out_size); if (end != NULL) *end = '\0'; - /* close */ + /- close -/ pclose(fp); return out; } - +*/ long str2int(const char *str) { long i; @@ -101,21 +102,23 @@ const char *set_server_logfile() { } void check_running_task(int pid) { - char cmd[256], filename[256] = ""; - snprintf(cmd, sizeof(cmd), "readlink -f /proc/%d/fd/1", command_line.taskpid); - linux_cmd(cmd, filename, sizeof(filename)); + char path[256], buff[256] = ""; + snprintf(path, 255, "/proc/%d/fd/1", command_line.taskpid); + int len = readlink(path, buff, sizeof(buff)); + - int namesize = strnlen(filename, 255)+1; - char* f = (char*) malloc(namesize); - strncpy(f, filename, namesize); - if (strlen(f) == 0) { + if (strlen(buff) == 0 || len == -1) { error("Client: PID[%d] is dead", pid); } else { - printf("Client: PID[%d] => %s\n", pid, filename); + printf("Client: PID[%d] => %s\n", pid, buff); } + int namesize = strnlen(buff, 255)+1; + char* f = (char*) malloc(namesize); + strncpy(f, buff, namesize); + struct stat t_stat; - snprintf(filename, 256, "/proc/%d/stat", pid); - if (stat(filename, &t_stat) != -1) { + snprintf(buff, 255, "/proc/%d/stat", pid); + if (stat(buff, &t_stat) != -1) { command_line.start_time = t_stat.st_ctime; } command_line.outfile = f; From 481a66717822ba5cd1ec17e022753e2e54837419 Mon Sep 17 00:00:00 2001 From: kylin Date: Mon, 27 Mar 2023 18:44:35 +0800 Subject: [PATCH 60/91] rename default_path.h --- default_path.h => default.inc | 0 sqlite.c | 2 +- user.c | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename default_path.h => default.inc (100%) diff --git a/default_path.h b/default.inc similarity index 100% rename from default_path.h rename to default.inc diff --git a/sqlite.c b/sqlite.c index 950db0f..40507a0 100644 --- a/sqlite.c +++ b/sqlite.c @@ -4,7 +4,7 @@ #include #include "main.h" -#include "default_path.h" +#include "default.inc" sqlite3 *db = NULL; diff --git a/user.c b/user.c index 9abf83d..9d514fd 100644 --- a/user.c +++ b/user.c @@ -10,7 +10,7 @@ #include "main.h" #include "user.h" -#include "default_path.h" +#include "default.inc" void send_list_line(int s, const char *str); void error(const char *str, ...); From dd549689186a4fcdd669e2329435484d870f4191 Mon Sep 17 00:00:00 2001 From: kylin Date: Mon, 27 Mar 2023 20:47:14 +0800 Subject: [PATCH 61/91] fixed some bugs --- execute.c | 3 ++- jobs.c | 40 ++++++++++------------------------------ user.c | 4 ++-- 3 files changed, 14 insertions(+), 33 deletions(-) diff --git a/execute.c b/execute.c index e85105a..290aa4f 100644 --- a/execute.c +++ b/execute.c @@ -26,7 +26,7 @@ /* from signals.c */ extern int signals_child_pid; /* 0, not set. otherwise, set. */ - +/**/ static int wait_for_pid(int pid) { char path[32]; @@ -36,6 +36,7 @@ static int wait_for_pid(int pid) close(in_fd); return -1; } + sprintf(path, "/proc/%i", pid); int dir_fd = open(path, 0); if (dir_fd < 0) { diff --git a/jobs.c b/jobs.c index c97eaaa..a88a888 100644 --- a/jobs.c +++ b/jobs.c @@ -326,7 +326,9 @@ int s_check_running_pid(int pid) { // char cmd[256], filename[256] = ""; // snprintf(cmd, sizeof(cmd), "readlink -f /proc/%d/fd/1", pid); // linux_cmd(cmd, filename, sizeof(filename)); - return kill(pid, 0) == 0; + int res = kill(pid, 0); + // printf("res = %d\n", res); + return res == 0; } // if any error return non-0; @@ -1313,13 +1315,7 @@ void job_finished(const struct Result *result, int jobid) { } static int fork_cmd(int UID, const char* path, const char* cmd) { int pid = -1; //定义一个进程ID变量 - int fd[2]; //定义一个管道数组 - // char buffer[10]; //定义一个缓冲区 - if (pipe(fd) < 0) //创建一个管道 - { - perror("pipe error"); //打印错误信息 - return -1; - } + pid = fork(); //调用fork()函数创建子进程 if (pid < 0) //如果返回值小于0,表示fork失败 { @@ -1328,19 +1324,7 @@ static int fork_cmd(int UID, const char* path, const char* cmd) { } else if (pid == 0) //如果返回值等于0,表示子进程正在运行 { - // printf("This is child process, pid = %d\n", getpid()); //打印子进程的ID - close(fd[0]); //关闭管道的读端 - /* - if (setuid(UID) < 0) { - sprintf(buffer, "%d", -1); //将子进程的ID转换为字符串 - } else { - sprintf(buffer, "%d", getpid()); //将子进程的ID转换为字符串 - } - write(fd[1], buffer, sizeof(buffer)); //将字符串写入管道的写端 - */ setuid(UID); - close(fd[1]); //关闭管道的写端 - if (path != NULL) chdir(path); system(cmd); exit(0); @@ -1360,12 +1344,7 @@ static int fork_cmd(int UID, const char* path, const char* cmd) { } else //如果返回值大于0,表示父进程正在运行 { - // printf("This is parent process, pid = %d\n", getpid()); //打印父进程的ID - // close(fd[1]); //关闭管道的写端 - // read(fd[0], buffer, sizeof(buffer)); //从管道的读端读取字符串 printf("[Child PID:%d] Add queued job: %s\n", pid, cmd); //打印子进程的ID - // close(fd[0]); //关闭管道的读端 - //wait(NULL); //等待子进程结束 } return pid; } @@ -1384,9 +1363,7 @@ static void s_add_job(struct Job* j, struct Job** p) { int slots = j->num_slots; user_busy[ts_UID] += slots; busy_slots += slots; - #ifdef TASKSET - set_task_cores(j, NULL); - #endif + } j->state = DELINK; @@ -1400,7 +1377,8 @@ static void s_add_job(struct Job* j, struct Job** p) { sprintf(c, " --relink %d -J %d ", j->pid, j->jobid); char* str = insert_chars(j->command_strip, j->command, c); - fork_cmd(user_UID[j->ts_UID], j->work_dir, str); + // fork_cmd(user_UID[j->ts_UID], j->work_dir, str); + fork_cmd(0, j->work_dir, str); jobids = jobids > j->jobid ? jobids : j->jobid + 1; j = NULL; @@ -1507,7 +1485,9 @@ void s_process_runjob_ok(int jobid, char *oname, int pid) { error("Job %i not running, but %i on runjob_ok", jobid, p->state); p->pid = pid; - p->output_filename = oname; + if (strlen(oname) != 0) { + p->output_filename = oname; + } pinfo_set_start_time_check(&p->info); insert_or_replace_DB(p, "Jobs"); diff --git a/user.c b/user.c index 9d514fd..8a432ad 100644 --- a/user.c +++ b/user.c @@ -106,9 +106,9 @@ void check_running_task(int pid) { snprintf(path, 255, "/proc/%d/fd/1", command_line.taskpid); int len = readlink(path, buff, sizeof(buff)); - + // printf("path = %s, buff = %s\n", path, buff); if (strlen(buff) == 0 || len == -1) { - error("Client: PID[%d] is dead", pid); + error("Client: PID[%d] is dead\n", pid); } else { printf("Client: PID[%d] => %s\n", pid, buff); } From 86fd2e12ec92f41cb36f84c74247ee07195f5699 Mon Sep 17 00:00:00 2001 From: kylin Date: Mon, 27 Mar 2023 23:43:58 +0800 Subject: [PATCH 62/91] add ptrace --- execute.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/execute.c b/execute.c index 290aa4f..1f13f41 100644 --- a/execute.c +++ b/execute.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,7 @@ /* from signals.c */ extern int signals_child_pid; /* 0, not set. otherwise, set. */ +extern int client_uid; /**/ static int wait_for_pid(int pid) @@ -36,7 +38,7 @@ static int wait_for_pid(int pid) close(in_fd); return -1; } - + sprintf(path, "/proc/%i", pid); int dir_fd = open(path, 0); if (dir_fd < 0) { @@ -61,8 +63,25 @@ static int wait_for_pid(int pid) return res; } +static int ptrace_pid(int pid) { + int status; + if (ptrace(PTRACE_ATTACH , pid, NULL) == -1) { + error("cannot attach to pid %d", pid); + } + waitpid(pid, &status, WUNTRACED); + printf("status = %d \n", status); + + if (ptrace(PTRACE_CONT , pid, NULL) == -1) { + error("cannot continue to pid %d", pid); + } + waitpid(pid, &status, 0); + printf("status = %d \n", status); + ptrace(PTRACE_DETACH, pid, NULL, NULL); + return status; +} + static void run_relink(int pid, struct Result *result) { - // int status = 0; + int status = 0; char *ofname = command_line.outfile; char *command; struct timeval endtv; @@ -73,8 +92,38 @@ static void run_relink(int pid, struct Result *result) { unblock_sigint_and_install_handler(); // printf("runjob_ok %s\n", ofname); c_send_runjob_ok(ofname, pid); + if (client_uid == 0) { + status = ptrace_pid(pid); + /* + char buff[1024]; + sprintf(buff, "strace -e none -e exit_group -p %d", pid); + status = system(buff); + // sprintf(buff, "%d", pid); + // status = execl("/usr/bin/strace", "strace", "-e", "none", "-e", "exit_group", "-p", buff, NULL); + + */ + } else { + status = wait_for_pid(pid); + } + + if (WIFEXITED(status)) { + /* We force the proper cast */ + signed char tmp; + tmp = WEXITSTATUS(status); + result->errorlevel = tmp; + result->died_by_signal = 0; + } else if (WIFSIGNALED(status)) { + signed char tmp; + tmp = WTERMSIG(status); + result->signal = tmp; + result->errorlevel = -1; + result->died_by_signal = 1; + } else { + result->died_by_signal = 0; + result->errorlevel = -1; + } + - wait_for_pid(pid); command = command_line.linux_cmd; // build_command_string(); if (command_line.send_output_by_mail) { From 8fd4cafd18678e2b82228f68b77327e7fc190dc5 Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 28 Mar 2023 14:25:51 +0800 Subject: [PATCH 63/91] fixed the bug and unify the source allocation --- Makefile | 2 +- README.md | 174 ++++++++++++++++++++++------------------ client.c | 13 ++- jobs.c | 232 ++++++++++++++++++++++++++++++++++-------------------- list.c | 7 +- main.c | 32 +++----- main.h | 6 +- relink.py | 15 ++-- server.c | 3 +- sqlite.c | 11 +++ taskset.c | 2 +- user.c | 17 ++-- user.txt | 2 +- 13 files changed, 304 insertions(+), 212 deletions(-) diff --git a/Makefile b/Makefile index d215c1e..564e22d 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ $(TARGET): $(OBJECTS) main.o: main.c main.h ifeq ($(GIT_REPO), true) GIT_VERSION=$$(echo $$(git describe --dirty --always --tags) | tr - +); \ - $(CC) $(CFLAGS) $(CPPFLAGS) -DTS_VERSION=$${GIT_VERSION} -c $< -o $@ + $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ endif user.o: user.c server_start.o: server_start.c main.h diff --git a/README.md b/README.md index c1f5b83..8562da3 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,109 @@ -# Task Spooler +# Task Spooler PLUS -Originally, [Task Spooler by Lluís Batlle i Rossell](https://vicerveza.homeunix.net/~viric/soft/ts/). I focked the task spooler and add the function to run is with multiple users. +This project is a fork of Task Spooler by [Task Spooler by Lluís Batlle i Rossell](https://vicerveza.homeunix.net/~viric/soft/ts/)., a software that offers basic task management. I have enhanced this software with more useful features, such as **multiple user support, fatal crash recovery, and processor allocation and binding.** The aim of this project is to provide an instant, standalone task management system, unlike *SLURM* and *PBS* which have complex installation and dependency issues. This task-spooler-PLUS is suitable for task management on PC and workstation with several to tens of users. ## Introduction -As in freshmeat.net: - -> task spooler is a Unix batch system where the tasks spooled run one after the other. The amount of jobs to run at once can be set at any time. Each user in each system has his own job queue. The tasks are run in the correct context (that of enqueue) from any shell/process, and its output/results can be easily watched. It is very useful when you know that your commands depend on a lot of RAM, a lot of disk use, give a lot of output, or for whatever reason it's better not to run them all at the same time, while you want to keep your resources busy for maximum benfit. Its interface allows using it easily in scripts. - -For your first contact, you can read an article at linux.com, -which I like as overview, guide and examples (original url). -On more advanced usage, don't neglect the TRICKS file in the package. +As a computer scientist, I often need to submit several to tens of simulation tasks on my own workstations and share the computational resources with other users. I tried the original task-spooler software, but it did not support multiple users. Everyone had their own task queue. Therefore, I modified the task-spooler and renamed it as **task-spooler-PLUS** to provide multiple user support. Recently, I also added fatal crash recovery and processor binding features. After a fatal crash, the task-spooler-PLUS can read the data from *Sqlite3* to recover all tasks, including running, queued, and finished ones. The processor binding is done through the *taskset* command. Unlike the original version, the task-spooler-PLUS server needs to run in the background with root privileges. ### Changelog See [CHANGELOG](CHANGELOG.md). -## Tutorial +## Features + +I enhanced the Task Spooler to run tasks on my workstation with multiple users. The following are the features of task-spooler-PLUS. -A tutorial with colab is available [here](https://librecv.github.io/blog/spooler/task%20manager/deep%20learning/2021/02/09/task-spooler.html). +* Task queue management for GNU/Linux, Darwin, Cygwin, and FreeBSD +* Multiple user support with the different limits on the maximum processors usage +* Fatal crush recovery by reading and writing the task log into Sqlite3 database +* Ability To pause and rerun any running or queued task +* Ability To stop or continue all tasks by a single user +* Good information output (default, json, and tab) +* Easy installation and configuration +* Optional separation of stdout and stderr -## Features +## Setup -I wrote Task Spooler because I didn't have any comfortable way of running batch jobs in my linux computer. I wanted to: +### Install Task Spooler PLUS -* Queue jobs from different terminals. -* Use it locally in my machine (not as in network queues). -* Have a good way of seeing the output of the processes (tail, errorlevels, ...). -* Easy use: almost no configuration. -* Easy to use in scripts. +Simple run the provided script -At the end, after some time using and developing ts, it can do something more: +``` +./make +``` +if you don't need the processors binding feature, try to remove `-DTASKSET` option of `CFLAGS`. -* It works in most systems I use and some others, like GNU/Linux, Darwin, Cygwin, and FreeBSD. -* No configuration at all for a simple queue. -* Good integration with renice, kill, etc. (through `ts -p` and process groups). -* Have any amount of queues identified by name, writting a simple wrapper script for each (I use ts2, tsio, tsprint, etc). -* Control how many jobs may run at once in any queue (taking profit of multicores). -* It never removes the result files, so they can be reached even after we've lost the ts task list. -* Transparent if used as a subprogram with -nf. -* Optional separation of stdout and stderr. +**The default positions** of log file and database is defined in `default.inc`. -![ts-sample](assets/sample.png) +```c +#define DEFAULT_USER_PATH "/home/kylin/task-spooler/user.txt" +#define DEFAULT_LOG_PATH "/home/kylin/task-spooler/log.txt" +#define DEFAULT_SQLITE_PATH "/home/kylin/task-spooler/task-spooler.db" +``` -## Setup +You can specific the positions by the environment variables `TS_USER_PATH`, `TS_LOGFILE_PATH`, and `TS_SQLITE_PATH`, respectively on the invoking of daemon server. Otherwise, you could specify the positions in the `user config` file. -### Install Task Spooler -Simple run the provided script + +In `taskset.c`, **the sequence of processors binding** is determined by there variables `MAX_CORE_NUM`,`MAX_CORE_NUM_HALF`, and `MAX_CORE_NUM_QUAD`. `MAX_CORE_NUM` defines the total number of processors in your computer. + +For a personal laptop with 2 CPU, and each CPU have 4 cores and 8 logical processors, by Hyper-threading. The optimal configuration would be: ``` -./install_cmake +#define MAX_CORE_NUM 16 +#define MAX_CORE_NUM_HALF 8 +#define MAX_CORE_NUM_QUAD 4 ``` -to use CMake, or + +For a AMD workstation with 2 CPU, 128 cores and 256 logical processors. The configuration would be: + ``` -./install_make +#define MAX_CORE_NUM 256 +#define MAX_CORE_NUM_HALF 128 +#define MAX_CORE_NUM_QUAD 64 ``` -to use Makefile. If Task Spooler has already been installed, and you want to reinstall, execute + +For a Intel workstation with 2 CPU, 128 cores and 128 logical processors. The configuration would be: ``` -./reinstall +#define MAX_CORE_NUM 128 +#define MAX_CORE_NUM_HALF 128 +#define MAX_CORE_NUM_QUAD 64 ``` -#### Local installation -To install without sudo privilege, one can use the following command +For the other hardware, the sequence of the processors could be specific manually as: + ``` -make install-local +static int core_id[MAX_CORE_NUM] = {0, 4, 1, 5, 2, 6, 3, 7} ``` -Note that, the installation will create a `bin` folder in `$HOME` if it does not exist. -To use `ts` anywhere, `$HOME/bin` needs to be added to `$PATH` if it hasn't been done already. + + +To use `ts` anywhere, `ts` needs to be added to `$PATH` if it hasn't been done already. To use `man`, you may also need to add `$HOME/.local/share/man` to `$MANPATH`. #### Common problems -* Cannot find CUDA: Did you set a `CUDA_HOME` flag? -* `/usr/bin/ld: cannot find -lnvidia-ml`: This lib lies in `$CUDA_HOME/lib64/stubs`. -Please append this path to `LD_LIBRARY_PATH`. -Sometimes, this problem persists even after adding the lib path. -Then one can add `-L$(CUDA_HOME)/lib64/stubs` to [this line](./Makefile#L29) in the Makefile. -* list.c:22:5: error: implicitly declaring library function 'snprintf' with type 'int (char *, unsigned long, const char *, ...)': Please remove `-D_XOPEN_SOURCE=500 -D__STRICT_ANSI__` in the Makefile as reported [here](https://github.com/justanhduc/task-spooler/issues/4). +* Once the suspending of the task-spooler Plus client: try to remove the socket file `/tmp/socket-ts.root` define by `TS_SOCKET` +* After a fatal crash, the recovered server cannot capture the exit-code and signal of the running task +## User configuration -### Uinstall Task Spooler +using the `TS_USER_PATH` environment variable to specify the path to the user configuration. The format of the user file is shown as an example. The UID could be found by `id -u [user]`. ``` -./uninstall +# 1231 # comments +TS_SLOTS = 4 # The number of slots +# uid name slots +1000 Kylin 10 +3021 test1 10 +1001 test0 100 +34 user2 30 + +qweq qweq qweq # error, automatically skipped ``` -Why would you want to do that anyway? + +Note that the number of slots could be specified in the user configuration file (2nd line). ## Mailing list @@ -120,11 +137,15 @@ Eric Keller wrote a nodejs web server showing the status of the task spooler que Duc Nguyen took the project and develops a GPU-support version. +Kylin wrote the multiple user support, fatal crush recovery through Sqlite3 database and processing binding via taskset + ## Manual See below or `man ts` for more details. ``` +Task Spooler 2.0.0 - a task queue system for the unix user. +Copyright (C) 2007-2023 Kylin JIANG - Duc Nguyen - Lluis Batlle i Rossell usage: ts [action] [-ngfmdE] [-L ] [-D ] [cmd...] Env vars: TS_SOCKET the path to the unix socket used by the ts command. @@ -137,6 +158,7 @@ Env vars: TS_SLOTS amount of jobs which can run at once, read on server start. TS_USER_PATH path to the user configuration file, read on server starts. TS_LOGFILE_PATH path to the job log file, read on server starts + TS_SQLITE_PATH path to the job log file, read on server starts TS_FIRST_JOBID The first job ID (default: 1000), read on server starts. TS_SORTJOBS Switch to control the job sequence sort, read on server starts. TMPDIR directory where to place the output files and the default socket. @@ -146,23 +168,24 @@ Long option actions: --unsetenv [var] remove the specified flag from server environment. --get_label || -a [id] show the job label. Of the last added, if not specified. --full_cmd || -F [id] show full command. Of the last added, if not specified. - --count_running || -R return the number of running jobs + --check_daemon Check the daemon is running or not. --count_running || -R return the number of running jobs --last_queue_id || -q show the job ID of the last added. --get_logdir get the path containing log files. --set_logdir [path] set the path containing log files. - --plain list jobs in plain tab-separated texts. + --serialize || -M [format] serialize the job list to the specified format. + Choices: {default, json, tab}. --daemon Run the server as daemon by Root only. - --hold [jobid] hold on a task. - --restart [jobid] rerun a hold task. - --lock Locker the server (Timeout: 30 sec.)For Root, timeout is infinity. + --pause [jobid] hold on a task. + --rerun [jobid] rerun a paused task. + --lock Locker the server (Timeout: 30 sec.) For Root, timeout is infinity. --unlock Unlocker the server. --stop [user] For normal user, pause all tasks and lock the account. For root, to lock all users or single [user]. --cont [user] For normal user, continue all paused tasks and lock the account. For root, to unlock all users or single [user]. - --pid [PID] Relink the running tasks by its [PID] from an expected failure. - --stime [start_time] Set the relinked task by starting time (Unix epoch). - Actions: + --relink [PID] Relink the running tasks by its [PID] from an expected failure. + --job [joibid] || -J [joibid] set the jobid of the new or relink job +Actions: -A Show all users information -X Refresh the user config by UID (Max. 100 users and only available for root) -K kill the task spooler server (only available for root) @@ -198,36 +221,36 @@ Options adding jobs: -N [num] number of slots required by the job (1 default). ``` -## User configuration -using the `TS_USER_PATH` environment variable to specify the path to the user configuration. The format of the user file is shown as an example. The UID could be found by `id -u [user]`. -``` -# 1231 # comments -# TS_SLOTS = 4 # environment variable -# TS_FIRST_JOBID = 2000 # environment variable -# uid name slots -1000 Kylin 10 -3021 test1 10 -1001 test0 100 -34 user2 30 - -qweq qweq qweq # automatically skipped -``` +## Restore from a fatal crush +Once, the task-spooler-PLUS server is crushed. The service would automatically recover all the tasks. Otherwise, we could do it manually via a automatically python script by `python relink.py`. -## Restore from a failure -To run the `relink.py` file with the user_log file, it would automatically relink all running tasks in a new task-spooler services. ``` # relink.py setup logfile = "/home/kylin/task-spooler/log.txt" # Path to the log file of tasks days_num = 10 # only tasks starts within [days_num] will be relinked ``` -## Thanks +or through the command line as + +``` +ts -N 10 --relink [pid] task-argv ... +ts -L myjob -N 4 --relink [pid] -J [Jobid] task-argv ... +``` + +where `[pid]` is the `PID ` of the running task and `[Jobid]` is the specified job id. **Author** + +- Kylin JIANG, gengpingjing@wust.edu.cn + - Duc Nguyen, + - Lluís Batlle i Rossell, + + **Acknowledgement** + * To Raúl Salinas, for his inspiring ideas * To Alessandro Öhler, the first non-acquaintance user, who proposed and created the mailing list. * Андрею Пантюхину, who created the BSD port. @@ -235,6 +258,7 @@ days_num = 10 # only tasks starts within [days_num] will be relinked * To Alexander V. Inyukhin, for the debian packages. * To Pascal Bleser, for the SuSE packages. * To Sergio Ballestrero, who sent code and motivated the development of a multislot version of ts. +* To Duc Nguyen, for his faithful working on GPU versions * To GNU, an ugly but working and helpful ol' UNIX implementation. **Software** diff --git a/client.c b/client.c index e0dec62..d2ec8f1 100644 --- a/client.c +++ b/client.c @@ -486,15 +486,16 @@ static char *get_output_file(int *pid) { } void c_pause_job(int jobid) { - int pid = 0; /* This will exit if there is any error */ + /* + int pid = 0; get_output_file(&pid); if (pid == -1 || pid == 0) { - fprintf(stderr, "Error: strange PID received: %i\n", pid); + fprintf(stderr, "Error: PID received: %i\n", pid); exit(-1); } - +*/ // printf("kill the pid: %d\n", pid); /* Send SIGTERM to the process group, as pid is for process group */ // kill(-pid, SIGSTOP); @@ -504,19 +505,18 @@ void c_pause_job(int jobid) { m.jobid = jobid; send_msg(server_socket, &m); c_wait_server_lines(); - kill_pid(pid, "kill -s STOP", NULL); } void c_rerun_job(int jobid) { + /* int pid = 0; - /* This will exit if there is any error */ get_output_file(&pid); if (pid == -1 || pid == 0) { fprintf(stderr, "Error: strange PID received: %i\n", pid); exit(-1); } - + */ // printf("kill the pid: %d\n", pid); /* Send SIGTERM to the process group, as pid is for process group */ // kill(-pid, SIGCONT); @@ -528,7 +528,6 @@ void c_rerun_job(int jobid) { // not error, restart job if (wait_server_lines_and_check("Error") == 0) { c_wait_server_lines(); - kill_pid(pid, "kill -s CONT", NULL); } } diff --git a/jobs.c b/jobs.c index a88a888..054f1f2 100644 --- a/jobs.c +++ b/jobs.c @@ -83,29 +83,23 @@ static void free_cores(struct Job* p) { #endif } -static void allocate_cores(struct Job* p) { +static void allocate_cores_ex(struct Job* p, const char* extra) { if (p == NULL) return; int ts_UID = p->ts_UID; user_busy[ts_UID] += p->num_slots; busy_slots += p->num_slots; - // user_queue[ts_UID]++; user_jobs[ts_UID]++; -#ifdef TASKSET - set_task_cores(p, NULL); -#endif + if (p -> state == RUNNING) { + #ifdef TASKSET + set_task_cores(p, extra); + #else + kill_pid(pid, extra, NULL); + #endif + } } -static void allocate_cores_and_cont(struct Job* p) { - if (p == NULL) return; - int ts_UID = p->ts_UID; - user_busy[ts_UID] += p->num_slots; - busy_slots += p->num_slots; - // user_queue[ts_UID]++; - user_jobs[ts_UID]++; -#ifdef TASKSET - set_task_cores(p, "kill -s CONT"); -#endif -} +static void allocate_cores(struct Job* p) { allocate_cores_ex(p, NULL); } + /* Serialize a job and add it to the JSON array. Returns 1 for success, 0 for failure. */ static int add_job_to_json_array(struct Job *p, cJSON *jobs) { @@ -371,7 +365,7 @@ int check_running_dead(int jobid) { struct Job* p = findjob(jobid); if (p->pid != 0 && p->state == RUNNING) { // a task is allocated by a pid and is running - return check_ifsleep(p->pid) == -1; + return is_sleep(p->pid) == -1; } return 0; } @@ -502,7 +496,7 @@ void s_get_label(int s, int jobid) { } if (p == 0) { - snprintf(buff, 255, "Job %i not finished or not running.\n", jobid); + snprintf(buff, 255, "[get_label0] Job %i not finished or not running.\n", jobid); send_list_line(s, buff); return; } @@ -542,7 +536,7 @@ void s_send_cmd(int s, int jobid) { } if (p == 0) { - snprintf(buff, 255, "Job %i not finished or not running.\n", jobid); + snprintf(buff, 255, "[get_label1] Job %i not finished or not running.\n", jobid); send_list_line(s, buff); return; } @@ -557,6 +551,7 @@ void s_mark_job_running(int jobid) { p = findjob(jobid); if (!p) error("Cannot mark the jobid %i RUNNING.", jobid); + allocate_cores(p); p->state = RUNNING; } @@ -596,6 +591,9 @@ const char *jstate2string(enum Jobstate s) { case DELINK: jobstate = "delink "; break; + case LOCKED: + jobstate = "locked "; + break; } return jobstate; } @@ -846,28 +844,29 @@ static int find_last_stored_jobid_finished() { /* Returns job id or -1 on error */ int s_newjob(int s, struct Msg *m, int ts_UID) { - struct Job *p; + struct Job *p = NULL; int res; - int waitjob_flag = 0; // 0 for newjob, 1 for WAIT and 2 for DELINK + // int waitjob_flag = 0; // 0 for newjob, 1 for WAIT and 2 for DELINK if (m->jobid != 0) { p = findjob(m->jobid); // if p == NULL => Manual Relink if (p != NULL) { // WAIT for restore queued tasks if (p->state == DELINK) { - waitjob_flag = 2; - p->state = RELINK; + ; // waitjob_flag = 2; } else if (p->state == WAIT) { // jobDB_wait_num--; - waitjob_flag = 1; - p->state = QUEUED; + ; // waitjob_flag = 1; + } else if (p->state == LOCKED) { + // jobDB_wait_num--; + ; // waitjob_flag = 1; } else { return -1; } } } - if (waitjob_flag == 0) { + if (p == NULL) { p = newjobptr(); if (m->jobid != 0) { p->jobid = m->jobid; @@ -879,11 +878,14 @@ int s_newjob(int s, struct Msg *m, int ts_UID) { p->state = QUEUED; } else p->state = HOLDING_CLIENT; + + // manually relink + if (m->u.newjob.taskpid != 0) { + p->state = RELINK; + } } // save the ts_UID and record the number of waiting jobs p->ts_UID = ts_UID; // get_tsUID(m->uid); - user_queue[p->ts_UID]++; - p->num_slots = m->u.newjob.num_slots; p->store_output = m->u.newjob.store_output; p->should_keep_finished = m->u.newjob.should_keep_finished; @@ -985,7 +987,7 @@ int s_newjob(int s, struct Msg *m, int ts_UID) { if (p->depend_on_size == 0) p->depend_on = 0; - if (waitjob_flag != 2) { + if (p->state != DELINK && p->state != WAIT && p->state != LOCKED) { pinfo_init(&p->info); pinfo_set_enqueue_time(&p->info); } @@ -1000,12 +1002,8 @@ int s_newjob(int s, struct Msg *m, int ts_UID) { if (res == -1) error("wrong bytes received"); - if (1) { // waitjob_flag == 0 - p->command = buff; - p->command_strip = m->u.newjob.command_size_strip; - } else { - free(buff); - } + p->command = buff; + p->command_strip = m->u.newjob.command_size_strip; /* load the work dir */ p->work_dir = 0; @@ -1050,26 +1048,27 @@ int s_newjob(int s, struct Msg *m, int ts_UID) { free(ptr); } - if (waitjob_flag == 0) { - // real new job - if (m->u.newjob.taskpid == 0) { - insert_DB(p, "Jobs"); - } else { + if (p->state == DELINK) { + p->state = RELINK; + // manually insert + } else if (p->state == WAIT) { + p->state = QUEUED; + user_queue[p->ts_UID]++; + } else if (p->state == RELINK) { /* for manually relink running task */ - p->pid = m->u.newjob.taskpid; - struct Procinfo* pinfo = &(p->info); - pinfo->start_time.tv_sec = 0; - int num_slots = p->num_slots, id = p->ts_UID; - busy_slots += num_slots; - user_busy[id] += num_slots; - user_jobs[id]++; - p->state = RELINK; - p->info.start_time.tv_sec = m->u.newjob.start_time; - p->info.start_time.tv_usec = 0; - insert_or_replace_DB(p, "Jobs"); - } - } // waitjob_flag == 0 - + p->info.start_time.tv_sec = m->u.newjob.start_time; + p->info.start_time.tv_usec = 0; + insert_or_replace_DB(p, "Jobs"); + } else if (p->state == QUEUED) { + insert_DB(p, "Jobs"); + user_queue[p->ts_UID]++; + } else if (p->state == LOCKED) { + ; + } else { + insert_DB(p, "Jobs"); + user_queue[p->ts_UID]++; + } + set_jobids_DB(jobids); return p->jobid; } @@ -1106,6 +1105,12 @@ void s_delete_job(int jobid) { } /* -1 if no one should be run. */ +/* + next_run_job() + in `server.c` + s_mark_job_running(newjob); + s_runjob(newjob, conn); +*/ int next_run_job() { struct Job *p; @@ -1159,11 +1164,7 @@ int next_run_job() { int num_slots = p->num_slots, id = p->ts_UID; if (id == uid && free_slots >= num_slots && - user_max_slots[id] - user_busy[id] >= num_slots) { - busy_slots += num_slots; - user_busy[id] += num_slots; - - user_jobs[id]++; + user_max_slots[id] - user_busy[id] >= num_slots) { user_queue[id]--; return p->jobid; } @@ -1210,8 +1211,9 @@ static void new_finished_job(struct Job *j) { p->next = j; p->next->next = 0; - delete_DB(j->jobid, "Jobs"); insert_DB(j, "Finished"); + delete_DB(j->jobid, "Jobs"); + #ifdef TASKSET unlock_core_by_job(j); #endif @@ -1264,9 +1266,7 @@ void job_finished(const struct Result *result, int jobid) { * we call this to clean up the jobs list in case of the client closing the * connection. */ if (p->state == RUNNING) { - busy_slots = busy_slots - p->num_slots; - user_busy[p->ts_UID] -= p->num_slots; - user_jobs[p->ts_UID]--; + free_cores(p); } /* Mark state */ @@ -1354,19 +1354,20 @@ static void s_add_job(struct Job* j, struct Job** p) { if (j->state == RUNNING) { if (j->pid > 0 && s_check_running_pid(j->pid) == 1) { printf("add job %d\n", j->jobid); - + + /* int ts_UID = j->ts_UID; user_jobs[ts_UID]++; - // if (j->state == RUNNING && check_ifsleep(j->pid) == 0) { - if (check_ifsleep(j->pid) == 0) { + // if (j->state == RUNNING && is_sleep(j->pid) == 0) { + if (is_sleep(j->pid) == 0) { int slots = j->num_slots; user_busy[ts_UID] += slots; busy_slots += slots; - } - j->state = DELINK; + */ + j->state = DELINK; // jobDB_Jobs[jobDB_num] = j; // jobDB_num++; @@ -1383,11 +1384,14 @@ static void s_add_job(struct Job* j, struct Job** p) { jobids = jobids > j->jobid ? jobids : j->jobid + 1; j = NULL; } else { - delete_DB(j->jobid, "JObs"); + delete_DB(j->jobid, "Jobs"); } - } else if (j->state == QUEUED) { + } else if (j->state == QUEUED || j->state == LOCKED) { printf("add the queue job %d\n", j->jobid); - j->state = WAIT; + if (j->state == QUEUED) { + j->state = WAIT; + } + // jobDB_wait_num++; (*p)->next = j; (*p) = j; @@ -1489,12 +1493,11 @@ void s_process_runjob_ok(int jobid, char *oname, int pid) { p->output_filename = oname; } pinfo_set_start_time_check(&p->info); - - insert_or_replace_DB(p, "Jobs"); - #ifdef TASKSET + if (pid >0 && is_sleep(pid) == 0) { + write_logfile(p); set_task_cores(p, NULL); - #endif - write_logfile(p); + } + insert_or_replace_DB(p, "Jobs"); } void s_send_runjob(int s, int jobid) { @@ -1555,7 +1558,7 @@ void s_job_info(int s, int jobid) { } if (p == 0) { - snprintf(buff, 255, "Job %i not finished or not running.\n", jobid); + snprintf(buff, 255, "[s_send_runjob] Job %i not finished or not running.\n", jobid); send_list_line(s, buff); return; } @@ -1638,8 +1641,8 @@ void s_cont_user(int s, int ts_UID) { if (p->ts_UID == ts_UID && p->state == RUNNING) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { - if (check_ifsleep(p->pid) == 1) { - allocate_cores_and_cont(p); + if (is_sleep(p->pid) == 1) { + allocate_cores_ex(p, "kill -s CONT"); } // kill_pid(p->pid, "kill -s CONT"); } @@ -1664,10 +1667,10 @@ void s_stop_user(int s, int ts_UID) { if (p->ts_UID == ts_UID && p->state == RUNNING) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { - if (check_ifsleep(p->pid) == 0) { + if (is_sleep(p->pid) == 0) { free_cores(p); + kill_pid(p->pid, "kill -s STOP", NULL); } - kill_pid(p->pid, "kill -s STOP", NULL); } else { char *label = "(...)"; if (p->label != NULL) @@ -1717,7 +1720,7 @@ void s_send_output(int s, int jobid) { if (jobid == -1) snprintf(buff, 255, "The last job has not finished or is not running.\n"); else - snprintf(buff, 255, "Job %i not finished or not running.\n", jobid); + snprintf(buff, 255, "[s_send_output] Job %i not finished or not running.\n", jobid); send_list_line(s, buff); return; } @@ -1993,6 +1996,23 @@ void s_unlock_server(int s, int ts_UID) { send_list_line(s, buff); } +static void s_lock_queue(struct Job* p) { + if (p->state == QUEUED) { + user_queue[p->ts_UID]--; + p->state = LOCKED; + set_state_DB(p->jobid, LOCKED); + } +} + +static void s_unlock_queue(struct Job* p) { + if (p->state == LOCKED) { + user_queue[p->ts_UID]++; + p->state = QUEUED; + set_state_DB(p->jobid, QUEUED); + } +} + + void s_pause_job(int s, int jobid, int ts_UID) { struct Job *p; p = findjob(jobid); @@ -2002,7 +2022,27 @@ void s_pause_job(int s, int jobid, int ts_UID) { return; } - if (check_ifsleep(p->pid) == 1) { + if (p->state == QUEUED) { + if (p->ts_UID == ts_UID || ts_UID == 0) { + snprintf(buff, 255, "The queued job [%d] is hold on.\n", jobid); + s_lock_queue(p); + send_list_line(s, buff); + // set_state_DB(jobid, p->state); + return; + } else { + snprintf(buff, 255, "Cannot hold on the queued job [%d].\n", jobid); + send_list_line(s, buff); + return; + } + } + + if (p->state == LOCKED) { + snprintf(buff, 255, "The queued job [%d] is already in locked.\n", jobid); + send_list_line(s, buff); + return; + } + + if (is_sleep(p->pid) == 1) { snprintf(buff, 255, "job [%d] is aleady in PAUSE.\n", jobid); send_list_line(s, buff); return; @@ -2010,6 +2050,7 @@ void s_pause_job(int s, int jobid, int ts_UID) { int job_tsUID = p->ts_UID; if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { free_cores(p); + kill_pid(p->pid, "kill -s STOP", NULL); snprintf(buff, 255, "To pause job [%d] successfully!\n", jobid); } else { snprintf(buff, 255, "Error: cannot pause job [%d]\n", jobid); @@ -2027,7 +2068,27 @@ void s_rerun_job(int s, int jobid, int ts_UID) { return; } - if (check_ifsleep(p->pid) == 0) { +if (p->state == LOCKED) { + if (p->ts_UID == ts_UID || ts_UID == 0) { + snprintf(buff, 255, "The locked job [%d] is in queue.\n", jobid); + s_unlock_queue(p); + // set_state_DB(jobid, p->state); + send_list_line(s, buff); + return; + } else { + snprintf(buff, 255, "Cannot unlock the locked job [%d].\n", jobid); + send_list_line(s, buff); + return; + } + } + + if (p->state == QUEUED) { + snprintf(buff, 255, "The job [%d] is already in queue.\n", jobid); + send_list_line(s, buff); + return; + } + + if (is_sleep(p->pid) == 0) { snprintf(buff, 255, "job [%d] is aleady in RUNNING.\n", jobid); send_list_line(s, buff); return; @@ -2037,7 +2098,7 @@ void s_rerun_job(int s, int jobid, int ts_UID) { if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { int num_slots = p->num_slots; if (user_busy[ts_UID] + num_slots < user_max_slots[ts_UID] && busy_slots + num_slots <= max_slots) { - allocate_cores(p); + allocate_cores_ex(p, "kill -s CONT"); snprintf(buff, 255, "To rerun job [%d] successfully!\n", jobid); } else { snprintf(buff, 255, "Error: not enough slots [%d]\n", jobid); @@ -2045,6 +2106,7 @@ void s_rerun_job(int s, int jobid, int ts_UID) { } else { snprintf(buff, 255, "Error: cannot rerun job [%d]\n", jobid); } + // kill_pid(p->pid, "kill -s CONT", NULL); send_list_line(s, buff); } /* Don't complain, if the socket doesn't exist */ diff --git a/list.c b/list.c index 8a974db..00a4841 100644 --- a/list.c +++ b/list.c @@ -18,8 +18,9 @@ extern int busy_slots; extern int max_slots; extern int core_usage; -/* return 0 for run and 1 for running and -1 for error */ -int check_ifsleep(int pid) { + +/* return 0 for running and 1 for sleep and -1 for error */ +int is_sleep(int pid) { char filename[256]; char name[256]; char status = '\0'; @@ -128,7 +129,7 @@ static char *print_noresult(const struct Job *p) { if (p->pid == 0) { jobstate = "N/A"; } else { - if (check_ifsleep(p->pid) == 1) { + if (is_sleep(p->pid) == 1) { jobstate = "pause"; } } diff --git a/main.c b/main.c index 02f0108..bd8e7a1 100644 --- a/main.c +++ b/main.c @@ -4,13 +4,6 @@ Please find the license in the provided COPYING file. */ -#define TS_VERSION_FALLBACK "1.3.1" - -/* from https://github.com/LLNL/lbann/issues/117 - * and https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html#Stringizing */ -#define TS_MAKE_STR(x) _TS_MAKE_STR(x) -#define _TS_MAKE_STR(x) #x - #include #include #include @@ -22,6 +15,8 @@ #include #include "main.h" +#include "version.h" + int client_uid; extern char *optarg; extern int optind, opterr, optopt; @@ -37,18 +32,9 @@ static char *old_getopt_env; static char version[1024]; static void init_version() { -#ifdef TS_VERSION - char *ts_version = TS_MAKE_STR(TS_VERSION); - snprintf(version, 1024, - "Task Spooler %s - a task queue system for the unix user.\n" - "Copyright (C) 2007-2020 Duc Nguyen - Lluis Batlle i Rossell", - ts_version); -#else - snprintf(version, 1024, - "Task Spooler %s - a task queue system for the unix user.\n" - "Copyright (C) 2007-2020 Duc Nguyen - Lluis Batlle i Rossell", - TS_VERSION_FALLBACK); -#endif + char *ts_version = TS_MAKE_STR(TS_VERSION); + sprintf(version, "Task Spooler %s - a task queue system for the unix user.\n" + "Copyright (C) 2007-%d Kylin JIANG - Duc Nguyen - Lluis Batlle i Rossell", ts_version, 2023); } static void default_command_line() { @@ -218,7 +204,7 @@ void parse_opts(int argc, char **argv) { if (command_line.taskpid <= 0) command_line.taskpid = 0; else { - check_running_task(command_line.taskpid); + check_relink(command_line.taskpid); } } else if (strcmp(longOptions[optionIdx].name, "stime") == 0) { command_line.start_time = str2int(optarg); @@ -563,6 +549,8 @@ static void print_help(const char *cmd) { "starts.\n"); printf(" TS_LOGFILE_PATH path to the job log file, read on server " "starts\n"); + printf(" TS_SQLITE_PATH path to the job log file, read on server " + "starts\n"); printf(" TS_FIRST_JOBID The first job ID (default: 1000), read on server " "starts.\n"); printf( @@ -598,7 +586,7 @@ static void print_help(const char *cmd) { printf(" --rerun [jobid] rerun a paused task.\n"); printf(" --lock Locker the server (Timeout: 30 " "sec.)" - "For Root, timeout is infinity.\n"); + " For Root, timeout is infinity.\n"); printf(" --unlock Unlocker the server.\n"); printf(" --stop [user] For normal user, pause all " "tasks and lock the account. \n " @@ -608,7 +596,7 @@ static void print_help(const char *cmd) { " For root, to unlock all users or single [user].\n"); printf(" --relink [PID] Relink the running tasks by its [PID] from an expected failure.\n"); printf( - " --job [joibid] || -J [joibid] set the new or relink job ID by jobid\n"); + " --job [joibid] || -J [joibid] set the jobid of the new or relink job\n"); // printf(" --stime [start_time] Set the relinked task by starting time (Unix epoch).\n"); printf("Actions:\n"); printf(" -A Show all users information\n"); diff --git a/main.h b/main.h index 7070599..89a80f6 100644 --- a/main.h +++ b/main.h @@ -162,6 +162,7 @@ enum Jobstate { RELINK, WAIT, DELINK, + LOCKED, }; struct Msg { @@ -545,14 +546,14 @@ int read_first_jobid_from_logfile(const char *path); void kill_pid(int ppid, const char *signal, const char* extra); // char* linux_cmd(char* CMD, char* out, int out_size); char **split_str(const char *str, int *size); -void check_running_task(int pid); +void check_relink(int pid); char *charArray_string(int num, char** array); /* locker */ int user_locker; time_t locker_time; int jobsort_flag; -int check_ifsleep(int pid); +int is_sleep(int pid); // int check_running_dead(int jobid); /* jobs.c */ @@ -599,6 +600,7 @@ void movetop_DB(int jobid); void swap_DB(int, int); void set_jobids_DB(int value); int get_jobids_DB(); +void set_state_DB(int jobid, int state); // int jobDB_num, jobDB_wait_num; // struct Job** jobDB_Jobs; diff --git a/relink.py b/relink.py index bb8cbd1..fa7a127 100755 --- a/relink.py +++ b/relink.py @@ -25,8 +25,9 @@ def parse(s): procs = int(s[p2+2:p3].strip()) user = s[p1+1:p2].strip() tag = s[p3+1:p4] + jobid = s[1:p1] t_time = datetime.datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S") - return user, procs, pid, tag, CMD, t_time + return user, procs, pid, tag, CMD, t_time, jobid; print("read from", logfile) @@ -40,7 +41,7 @@ def parse(s): print(" only restore tasks with {} days, start by".format(days_num), t_line) tasks = [] for l in lines[:]: - user, procs, pid, tag, CMD, t_time = parse(l) + user, procs, pid, tag, CMD, t_time, jobid = parse(l) if (psutil.pid_exists(pid)): if (t_time > t_line): @@ -48,19 +49,21 @@ def parse(s): cmd = " ".join(p.cmdline()).replace("/bin/sh /opt/intel/oneapi/mpi/2021.3.0/bin/mpirun", "mpirun") if (cmd == CMD): print("add:", l) - tasks.append([tag, pid, procs, CMD]) + tasks.append([tag, pid, jobid, procs, CMD]) # tasks.append([tag, pid, procs, int(t_time.timestamp()), CMD]) else: - print(" N/A:", l) # "#", cmd, p.name(), p.cmdline()) + print("add:", l) + tasks.append([tag, pid, jobid, procs, CMD]) + # print(" N/A:", l) # "#", cmd, p.name(), p.cmdline()) else: print(" UNK:", l) for i in tasks[:]: if i[0] == "..": - CMD = '{} --relink {} -N {} "{}"'.format(ts_CMD, *i[1:]) + CMD = '{} --relink {} -J {} -N {} "{}"'.format(ts_CMD, *i[1:]) # CMD = 'ts --pid {} -N {} --stime {:} "{}"'.format(*i[1:]) else: - CMD = '{} -L {} --relink {} -N {} "{}"'.format(ts_CMD, *i) + CMD = '{} -L {} --relink {} -J {} -N {} "{}"'.format(ts_CMD, *i) # CMD = 'ts -L {} --pid {} -N {} --stime {:} "{}"'.format(*i) print(CMD) os.system(CMD) diff --git a/server.c b/server.c index fea7d29..a1cc758 100644 --- a/server.c +++ b/server.c @@ -42,7 +42,7 @@ enum { MAXCONN = 1000 }; enum Break { BREAK, NOBREAK, CLOSE }; char *logdir; - +extern int busy_slots; /* Prototypes */ static void server_loop(int ls); @@ -379,6 +379,7 @@ static void server_loop(int ls) { int conn, awaken_job; conn = get_conn_of_jobid(newjob); /* This next marks the firstjob state to RUNNING */ + s_mark_job_running(newjob); s_runjob(newjob, conn); diff --git a/sqlite.c b/sqlite.c index 40507a0..957fcd0 100644 --- a/sqlite.c +++ b/sqlite.c @@ -276,6 +276,17 @@ static void set_order_id_DB(int jobid, int order_id) { } } +void set_state_DB(int jobid, int state) { + char *err_msg = 0; + char sql[1024]; + sprintf(sql, "UPDATE Jobs SET state=%d WHERE jobid=%d", state, jobid); + int rc = sqlite3_exec(db, sql, 0, 0, &err_msg); + if (rc != SQLITE_OK ) { + fprintf(stderr, "[movetop_DB] SQL error: %s\n", err_msg); + sqlite3_free(err_msg); + } +} + void swap_DB(int jobid0, int jobid1) { int id0 = get_order_id(jobid0); int id1 = get_order_id(jobid1); diff --git a/taskset.c b/taskset.c index 9fcd5c3..7b7fd98 100644 --- a/taskset.c +++ b/taskset.c @@ -65,7 +65,7 @@ void unlock_core_by_job(struct Job* p) { } int set_task_cores(struct Job* p, const char* extra) { - if (p == NULL || p->pid <= 0 || p->state != RUNNING) return -1; + if (p == NULL || p->pid <= 0) return -1; #ifdef TASKSET int N = p->num_slots; diff --git a/user.c b/user.c index 8a432ad..b347ea4 100644 --- a/user.c +++ b/user.c @@ -101,7 +101,7 @@ const char *set_server_logfile() { return logfile_path; } -void check_running_task(int pid) { +void check_relink(int pid) { char path[256], buff[256] = ""; snprintf(path, 255, "/proc/%d/fd/1", command_line.taskpid); int len = readlink(path, buff, sizeof(buff)); @@ -267,12 +267,12 @@ void s_user_status_all(int s) { char buffer[256]; char *extra; send_list_line(s, "-- Users ----------- \n"); - for (size_t i = 0; i < user_number; i++) { + for (int i = 0; i < user_number; i++) { extra = user_locked[i] != 0 ? "Locked" : ""; if (user_max_slots[i] == 0 && user_busy[i] == 0) continue; - snprintf(buffer, 256, "[%04d] %3d/%-4d %20s Run. %2d %s\n", user_UID[i], - user_busy[i], abs(user_max_slots[i]), user_name[i], user_jobs[i], - extra); + snprintf(buffer, 256, "[%04d] %3d/%-4d Q:%-3d %16s Run. %2d %s\n", user_UID[i], + user_busy[i], abs(user_max_slots[i]), user_queue[i], user_name[i], user_jobs[i], + extra); send_list_line(s, buffer); } snprintf(buffer, 256, "Service at UID:%d\n", server_uid); @@ -283,10 +283,10 @@ void s_user_status_all(int s) { void s_user_status(int s, int i) { char buffer[256]; char *extra = ""; - if (user_locked[i] != 0) + if (user_locked[i] != 0) extra = "Locked"; - snprintf(buffer, 256, "[%04d] %3d/%-4d %20s Run. %2d %s\n", user_UID[i], - user_busy[i], abs(user_max_slots[i]), user_name[i], user_jobs[i], + snprintf(buffer, 256, "[%04d] %3d/%-4d Q:%-3d %16s Run. %2d %s\n", user_UID[i], + user_busy[i], abs(user_max_slots[i]), user_queue[i], user_name[i], user_jobs[i], extra); send_list_line(s, buffer); } @@ -302,6 +302,7 @@ int get_tsUID(int uid) { void kill_pid(int pid, const char *signal, const char* extra) { + if (signal == NULL && extra == NULL) return; char command[1024]; const char *path = get_kill_sh_path(); if (extra == NULL) { diff --git a/user.txt b/user.txt index a21bbd8..0a4c698 100644 --- a/user.txt +++ b/user.txt @@ -1,6 +1,6 @@ # 1231 # comments TS_SLOTS = 4 # environment variable -# TS_FIRST_JOBID = 2000 # environment variable +TS_FIRST_JOBID = 2000 # environment variable # uid name slots 1000 Kylin 10 3021 test1 10 From e4c1e08360545816997361c0bf6888344a6e78d0 Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 28 Mar 2023 20:48:47 +0800 Subject: [PATCH 64/91] change the way to allocate and free cores on jobs --- error.c | 18 +++++------ jobs.c | 96 +++++++++++++++++++++++++++++++++++-------------------- list.c | 5 +-- main.h | 1 + server.c | 18 ++++++++--- taskset.c | 3 +- user.c | 22 ++++--------- version.h | 17 ++++++++++ 8 files changed, 113 insertions(+), 67 deletions(-) create mode 100644 version.h diff --git a/error.c b/error.c index 457539b..8328c8e 100644 --- a/error.c +++ b/error.c @@ -149,10 +149,10 @@ void error(const char *str, ...) { real_errno = errno; - if (process_type == CLIENT) { - vfprintf(stderr, str, ap); - fputc('\n', stderr); - } + // if (process_type == CLIENT) { + vfprintf(stderr, str, ap); + fputc('\n', stderr); + // } problem(ERROR, str, ap); close_sqlite(); @@ -167,11 +167,11 @@ void debug(const char *str, ...) real_errno = errno; - if (process_type == CLIENT) - { - vfprintf(stderr, str, ap); - fputc('\n', stderr); - } + //if (process_type == CLIENT) + // { + vfprintf(stderr, str, ap); + fputc('\n', stderr); + //} problem(DEBUG, str, ap); } diff --git a/jobs.c b/jobs.c index 054f1f2..c4b2bbc 100644 --- a/jobs.c +++ b/jobs.c @@ -72,10 +72,11 @@ static void destroy_job(struct Job *p) { } static void free_cores(struct Job* p) { - if (p == NULL) return; + if (p == NULL && p->num_allocated == 0) return; int ts_UID = p->ts_UID; user_busy[ts_UID] -= p->num_slots; busy_slots -= p->num_slots; + p->num_allocated = 0; // user_queue[ts_UID]--; user_jobs[ts_UID]--; #ifdef TASKSET @@ -88,6 +89,7 @@ static void allocate_cores_ex(struct Job* p, const char* extra) { int ts_UID = p->ts_UID; user_busy[ts_UID] += p->num_slots; busy_slots += p->num_slots; + p->num_allocated = p->num_slots; user_jobs[ts_UID]++; if (p -> state == RUNNING) { #ifdef TASKSET @@ -291,7 +293,6 @@ static struct Job *find_previous_job(const struct Job *final) { struct Job *findjob(int jobid) { struct Job *p; - /* Show Queued or Running jobs */ p = firstjob.next; while (p != 0) { @@ -299,7 +300,6 @@ struct Job *findjob(int jobid) { return p; p = p->next; } - return NULL; } @@ -360,16 +360,6 @@ int s_check_relink(int s, int pid, int ts_UID) { return job_tsUID; } -/* -int check_running_dead(int jobid) { - struct Job* p = findjob(jobid); - if (p->pid != 0 && p->state == RUNNING) { - // a task is allocated by a pid and is running - return is_sleep(p->pid) == -1; - } - return 0; -} -*/ static struct Job *findjob_holding_client() { struct Job *p; @@ -546,11 +536,43 @@ void s_send_cmd(int s, int jobid) { free(cmd); } +static char* get_ofile_from_FD(int pid) { + char path[256], buff[256] = ""; + snprintf(path, 255, "/proc/%d/fd/1", command_line.taskpid); + int len = readlink(path, buff, sizeof(buff)); + + // printf("path = %s, buff = %s\n", path, buff); + if (strlen(buff) == 0 || len == -1) { + return NULL; + } + int namesize = strnlen(buff, 255)+1; + char* f = (char*) malloc(namesize); + strncpy(f, buff, namesize); + return f; +} + void s_mark_job_running(int jobid) { struct Job *p; p = findjob(jobid); if (!p) error("Cannot mark the jobid %i RUNNING.", jobid); + if (p->state == RELINK) { + if (p->output_filename == NULL) { + p->output_filename = get_ofile_from_FD(p->pid); + } + if (is_sleep(p->pid) == 1) { + p->state = RUNNING; + return; + } + } + + /* + int ts_UID = p->ts_UID; + user_busy[ts_UID] += p->num_slots; + busy_slots += p->num_slots; + user_jobs[ts_UID]++; + p->num_allocated = p->num_slots + */ allocate_cores(p); p->state = RUNNING; } @@ -882,6 +904,7 @@ int s_newjob(int s, struct Msg *m, int ts_UID) { // manually relink if (m->u.newjob.taskpid != 0) { p->state = RELINK; + printf("relink to pid: %d\n", m->u.newjob.taskpid); } } // save the ts_UID and record the number of waiting jobs @@ -1056,6 +1079,7 @@ int s_newjob(int s, struct Msg *m, int ts_UID) { user_queue[p->ts_UID]++; } else if (p->state == RELINK) { /* for manually relink running task */ + p->pid = m->u.newjob.taskpid; p->info.start_time.tv_sec = m->u.newjob.start_time; p->info.start_time.tv_usec = 0; insert_or_replace_DB(p, "Jobs"); @@ -1251,21 +1275,22 @@ static int in_notify_list(int jobid) { /* job_finished from running to jobid */ void job_finished(const struct Result *result, int jobid) { - struct Job *p; - + // printf("job_finished %d\n", jobid); + if (busy_slots < 0) error( "Wrong state in the server. busy_slots = %i instead of greater than 0", busy_slots); + + struct Job *p = findjob(jobid); - p = findjob(jobid); if (p == NULL) error("on jobid %i finished, it doesn't exist", jobid); /* The job may be not only in running state, but also in other states, as * we call this to clean up the jobs list in case of the client closing the * connection. */ - if (p->state == RUNNING) { + if (p->num_allocated != 0) { free_cores(p); } @@ -1278,6 +1303,7 @@ void job_finished(const struct Result *result, int jobid) { p->result = *result; last_finished_jobid = p->jobid; notify_errorlevel(p); + pinfo_set_end_time(&p->info); if (result->real_ms == 0) { p->info.start_time = p->info.enqueue_time = p->info.end_time; @@ -1312,6 +1338,7 @@ void job_finished(const struct Result *result, int jobid) { jpointer->next = newfirst; } + } static int fork_cmd(int UID, const char* path, const char* cmd) { int pid = -1; //定义一个进程ID变量 @@ -1355,18 +1382,6 @@ static void s_add_job(struct Job* j, struct Job** p) { if (j->pid > 0 && s_check_running_pid(j->pid) == 1) { printf("add job %d\n", j->jobid); - /* - int ts_UID = j->ts_UID; - user_jobs[ts_UID]++; - - // if (j->state == RUNNING && is_sleep(j->pid) == 0) { - if (is_sleep(j->pid) == 0) { - int slots = j->num_slots; - user_busy[ts_UID] += slots; - busy_slots += slots; - } - */ - j->state = DELINK; // jobDB_Jobs[jobDB_num] = j; @@ -1378,8 +1393,8 @@ static void s_add_job(struct Job* j, struct Job** p) { sprintf(c, " --relink %d -J %d ", j->pid, j->jobid); char* str = insert_chars(j->command_strip, j->command, c); - // fork_cmd(user_UID[j->ts_UID], j->work_dir, str); - fork_cmd(0, j->work_dir, str); + fork_cmd(user_UID[j->ts_UID], j->work_dir, str); + // fork_cmd(0, j->work_dir, str); jobids = jobids > j->jobid ? jobids : j->jobid + 1; j = NULL; @@ -1481,6 +1496,7 @@ void s_clear_finished(int ts_UID) { // run the jobs void s_process_runjob_ok(int jobid, char *oname, int pid) { + // printf("s_process_runjob_ok \n "); struct Job *p; p = findjob(jobid); if (p == 0) @@ -1489,7 +1505,7 @@ void s_process_runjob_ok(int jobid, char *oname, int pid) { error("Job %i not running, but %i on runjob_ok", jobid, p->state); p->pid = pid; - if (strlen(oname) != 0) { + if (oname != NULL && strlen(oname) != 0) { p->output_filename = oname; } pinfo_set_start_time_check(&p->info); @@ -1638,10 +1654,11 @@ void s_cont_user(int s, int ts_UID) { struct Job *p = firstjob.next; while (p != NULL) { - if (p->ts_UID == ts_UID && p->state == RUNNING) { + if (p->ts_UID == ts_UID && p->state == LOCKED) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { if (is_sleep(p->pid) == 1) { + p->state = RUNNING; allocate_cores_ex(p, "kill -s CONT"); } // kill_pid(p->pid, "kill -s CONT"); @@ -1668,6 +1685,7 @@ void s_stop_user(int s, int ts_UID) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { if (is_sleep(p->pid) == 0) { + p->state = LOCKED; free_cores(p); kill_pid(p->pid, "kill -s STOP", NULL); } @@ -1864,7 +1882,6 @@ int s_remove_job(int s, int *jobid, int client_tsUID) { *jobid = p->jobid; delete_DB(p->jobid, "Jobs"); /* Tricks for the check_notify_list */ - printf("end job [%d] by remove cmd", p->jobid); p->state = FINISHED; p->result.errorlevel = -1; notify_errorlevel(p); @@ -2014,6 +2031,11 @@ static void s_unlock_queue(struct Job* p) { void s_pause_job(int s, int jobid, int ts_UID) { + if (user_max_slots[ts_UID] < 0) { + snprintf(buff, 255, "Error: The owner `%s` is locked\n", user_name[ts_UID]); + send_list_line(s, buff); + return; + } struct Job *p; p = findjob(jobid); if (p == 0) { @@ -2047,6 +2069,7 @@ void s_pause_job(int s, int jobid, int ts_UID) { send_list_line(s, buff); return; } + int job_tsUID = p->ts_UID; if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { free_cores(p); @@ -2059,6 +2082,11 @@ void s_pause_job(int s, int jobid, int ts_UID) { } void s_rerun_job(int s, int jobid, int ts_UID) { + if (user_max_slots[ts_UID] < 0) { + snprintf(buff, 255, "Error: The owner `%s` is locked\n", user_name[ts_UID]); + send_list_line(s, buff); + return; + } struct Job *p; p = findjob(jobid); diff --git a/list.c b/list.c index 00a4841..b851b0a 100644 --- a/list.c +++ b/list.c @@ -21,6 +21,7 @@ extern int core_usage; /* return 0 for running and 1 for sleep and -1 for error */ int is_sleep(int pid) { + // if (pid == 0) return -1; char filename[256]; char name[256]; char status = '\0'; @@ -29,12 +30,12 @@ int is_sleep(int pid) { fp = fopen(filename, "r"); if (fp == NULL) { - fprintf(stderr, "Error: Couldn't open [%s]\n", filename); + fprintf(stderr, "[is_sleep] Error: Couldn't open [%s]\n", filename); return -1; } int token = fscanf(fp, "%d %s %c", &pid, name, &status); if (token < 3) { - fprintf(stderr, "Error: not enough (3) tokens\n"); + fprintf(stderr, "[is_sleep] Error: not enough (3) tokens\n"); return -1; } fclose(fp); diff --git a/main.h b/main.h index 89a80f6..381ae83 100644 --- a/main.h +++ b/main.h @@ -245,6 +245,7 @@ struct Job { char *label; struct Procinfo info; int num_slots; + int num_allocated; #ifdef TASKSET char* cores; #endif diff --git a/server.c b/server.c index a1cc758..8774a85 100644 --- a/server.c +++ b/server.c @@ -360,6 +360,7 @@ static void server_loop(int ls) { warning("Closing"); /* On unknown message, we close the client, or it may hang waiting for an answer */ + // printf("close to jobid %d nconnections = %d/%d\n", client_cs[i].jobid, i, nconnections); clean_after_client_disappeared(client_cs[i].socket, i); } else if (b == BREAK) { keep_loop = 0; @@ -372,14 +373,13 @@ static void server_loop(int ls) { */ } /* This will return firstjob->jobid or -1 */ - newjob = next_run_job(); - + // printf("end of next_run, newjob = %d\n", newjob); + if (newjob != -1) { int conn, awaken_job; conn = get_conn_of_jobid(newjob); /* This next marks the firstjob state to RUNNING */ - s_mark_job_running(newjob); s_runjob(newjob, conn); @@ -389,7 +389,8 @@ static void server_loop(int ls) { error("The job awaken does not have a connection open"); s_newjob_ok(wake_conn); } - } + // printf("end of next_run_job for jobid[%d]\n", newjob); + } // job != -1 } // end of while (keep_loop) end_server(ls); @@ -465,12 +466,13 @@ static void s_remove_all_queues(int ts_UID) { } static enum Break client_read(int index) { + // printf("client_read(%d)\n", index); + struct Msg m = default_msg(); int s; int res; s = client_cs[index].socket; - /* Read the message */ res = recv_msg(s, &m); if (res == -1) { @@ -481,6 +483,7 @@ static enum Break client_read(int index) { clean_after_client_disappeared(s, index); return NOBREAK; } + // printf("client_read(%d), m.type = %d\n", index, m.type); int ts_UID = client_cs[index].ts_UID; /* Process message */ @@ -633,9 +636,14 @@ static enum Break client_read(int index) { s_send_cmd(s, m.jobid); break; case ENDJOB: + // printf("job_finished = %x, jobid = %d\n", &m.u.result, client_cs[index].jobid); job_finished(&m.u.result, client_cs[index].jobid); + /* For the dependencies */ + // printf("check_notify_list\n"); + check_notify_list(client_cs[index].jobid); + // printf("check_notify_list0\n"); /* We don't want this connection to do anything * more related to the jobid, secially on remove_connection * when we receive the EOC. */ diff --git a/taskset.c b/taskset.c index 7b7fd98..d3adeb4 100644 --- a/taskset.c +++ b/taskset.c @@ -28,7 +28,7 @@ void init_taskset() { } int allocate_cores(int N) { - if (N > MAX_CORE_NUM - core_usage) return 0; + if (N + core_usage > MAX_CORE_NUM) return 0; task_core_num = 0; int i = 0; while(task_core_num < N && i < MAX_CORE_NUM) { @@ -44,6 +44,7 @@ int allocate_cores(int N) { } void lock_core_by_job(struct Job* p) { + if (p == NULL) return; for (int i = 0; i < task_core_num; i++) { int iA = task_array_id[i]; core_jobs[iA] = p; diff --git a/user.c b/user.c index b347ea4..4221509 100644 --- a/user.c +++ b/user.c @@ -102,29 +102,19 @@ const char *set_server_logfile() { } void check_relink(int pid) { - char path[256], buff[256] = ""; - snprintf(path, 255, "/proc/%d/fd/1", command_line.taskpid); - int len = readlink(path, buff, sizeof(buff)); - - // printf("path = %s, buff = %s\n", path, buff); - if (strlen(buff) == 0 || len == -1) { - error("Client: PID[%d] is dead\n", pid); - } else { - printf("Client: PID[%d] => %s\n", pid, buff); - } - int namesize = strnlen(buff, 255)+1; - char* f = (char*) malloc(namesize); - strncpy(f, buff, namesize); - + char buff[256]; struct stat t_stat; snprintf(buff, 255, "/proc/%d/stat", pid); if (stat(buff, &t_stat) != -1) { command_line.start_time = t_stat.st_ctime; + } else { + if (kill(pid, 0) != 0) { + error("Client: PID[%d] is dead\n", pid); + } } - command_line.outfile = f; + command_line.outfile = NULL; } - /* ts_UID from user_number is 1-indexing The minimal ts_UID for a valid user is 1; diff --git a/version.h b/version.h new file mode 100644 index 0000000..0037296 --- /dev/null +++ b/version.h @@ -0,0 +1,17 @@ +// +// Created by justanhduc on 24/09/2022. +// + +#ifndef TASK_SPOOLER_VERSION_H +#define TASK_SPOOLER_VERSION_H + +#ifndef TS_VERSION +#define TS_VERSION 2.0.0 +#endif + +/* from https://github.com/LLNL/lbann/issues/117 + * and https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html#Stringizing */ +#define TS_MAKE_STR(x) _TS_MAKE_STR(x) +#define _TS_MAKE_STR(x) #x + +#endif //TASK_SPOOLER_VERSION_H From f2f3dd673e8e84f212b029c36c87f34a0ae234b5 Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 28 Mar 2023 21:00:05 +0800 Subject: [PATCH 65/91] fixed the bug on s_rerun_job() --- jobs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jobs.c b/jobs.c index c4b2bbc..72b4f4a 100644 --- a/jobs.c +++ b/jobs.c @@ -2125,7 +2125,7 @@ if (p->state == LOCKED) { int job_tsUID = p->ts_UID; if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { int num_slots = p->num_slots; - if (user_busy[ts_UID] + num_slots < user_max_slots[ts_UID] && busy_slots + num_slots <= max_slots) { + if (user_busy[ts_UID] + num_slots <= user_max_slots[ts_UID] && busy_slots + num_slots <= max_slots) { allocate_cores_ex(p, "kill -s CONT"); snprintf(buff, 255, "To rerun job [%d] successfully!\n", jobid); } else { From 069df85bc442665c8d00aba14ec85275e539afca Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 28 Mar 2023 21:18:07 +0800 Subject: [PATCH 66/91] using while to wait for pid --- execute.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/execute.c b/execute.c index 1f13f41..822c8a5 100644 --- a/execute.c +++ b/execute.c @@ -28,7 +28,7 @@ extern int signals_child_pid; /* 0, not set. otherwise, set. */ extern int client_uid; -/**/ +/* static int wait_for_pid(int pid) { char path[32]; @@ -62,6 +62,13 @@ static int wait_for_pid(int pid) close(in_fd); return res; } +*/ +static int wait_for_pid(int pid) { + while(kill(pid, 0) == 0) { + sleep(1); + } + return -1; +} static int ptrace_pid(int pid) { int status; From 3efcd17de0ab41adc9da3d7bdf625921962d4d2d Mon Sep 17 00:00:00 2001 From: kylin Date: Tue, 28 Mar 2023 21:53:04 +0800 Subject: [PATCH 67/91] add the pause check --- jobs.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ main.h | 3 +++ server.c | 11 ++++++++--- 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/jobs.c b/jobs.c index 72b4f4a..3d36863 100644 --- a/jobs.c +++ b/jobs.c @@ -102,6 +102,52 @@ static void allocate_cores_ex(struct Job* p, const char* extra) { static void allocate_cores(struct Job* p) { allocate_cores_ex(p, NULL); } +int num_pause; +int* paused_pids = NULL; + +void init_pause() { + num_pause = 0; + paused_pids = (int*) malloc(sizeof(int) * max_jobs); +} + +static int set_pause(int pid) { + paused_pids[num_pause] = pid; + return num_pause++; +} + +static int free_pause(int pid) { + for (int i = 0; i < num_pause; i++) + { + if (paused_pids[i] == pid) { + num_pause--; + paused_pids[i] = paused_pids[num_pause]; + return 1; + } + } + return 0; +} + +void check_pause() { + int i = 0; + while (i < num_pause) { + int pid = paused_pids[i]; + int res = is_sleep(pid); + if (res == 0) { + kill_pid(pid, "kill -s STOP", NULL); + } else if(res == -1) { + num_pause--; + paused_pids[i] = paused_pids[num_pause]; + continue; + } + i++; + } + // printf("num_pause = %d\n", num_pause); +} + +void free_pause_array() { + free(paused_pids); + paused_pids = NULL; +} /* Serialize a job and add it to the JSON array. Returns 1 for success, 0 for failure. */ static int add_job_to_json_array(struct Job *p, cJSON *jobs) { @@ -561,6 +607,7 @@ void s_mark_job_running(int jobid) { p->output_filename = get_ofile_from_FD(p->pid); } if (is_sleep(p->pid) == 1) { + set_pause(p->pid); p->state = RUNNING; return; } @@ -1659,6 +1706,7 @@ void s_cont_user(int s, int ts_UID) { if (p->pid != 0) { if (is_sleep(p->pid) == 1) { p->state = RUNNING; + free_pause(p->pid); allocate_cores_ex(p, "kill -s CONT"); } // kill_pid(p->pid, "kill -s CONT"); @@ -1687,6 +1735,7 @@ void s_stop_user(int s, int ts_UID) { if (is_sleep(p->pid) == 0) { p->state = LOCKED; free_cores(p); + set_pause(p->pid); kill_pid(p->pid, "kill -s STOP", NULL); } } else { @@ -2072,6 +2121,7 @@ void s_pause_job(int s, int jobid, int ts_UID) { int job_tsUID = p->ts_UID; if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { + set_pause(p->pid); free_cores(p); kill_pid(p->pid, "kill -s STOP", NULL); snprintf(buff, 255, "To pause job [%d] successfully!\n", jobid); @@ -2126,6 +2176,7 @@ if (p->state == LOCKED) { if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { int num_slots = p->num_slots; if (user_busy[ts_UID] + num_slots <= user_max_slots[ts_UID] && busy_slots + num_slots <= max_slots) { + free_pause(p->pid); allocate_cores_ex(p, "kill -s CONT"); snprintf(buff, 255, "To rerun job [%d] successfully!\n", jobid); } else { diff --git a/main.h b/main.h index 381ae83..2d113df 100644 --- a/main.h +++ b/main.h @@ -576,6 +576,9 @@ void s_sort_jobs(); int s_check_relink(int s, int pid, int ts_UID); void s_read_sqlite(); int s_check_running_pid(int pid); +void init_pause(); +void check_pause(); +void free_pause_array(); struct Job *findjob(int jobid); /* client.c */ diff --git a/server.c b/server.c index 8774a85..5de643a 100644 --- a/server.c +++ b/server.c @@ -280,10 +280,10 @@ void server_main(int notify_fd, char *_path) { notify_parent(notify_fd); if (open_sqlite() != 0) { - debug_write("Cannot open sqlite database"); + // debug_write("Cannot open sqlite database"); error("Cannot open sqlite database"); } - + init_pause(); // printf("jobids = %d\n", get_jobids_DB()); jobsort_flag = get_env("TS_SORTJOBS", 0); s_set_jobids(get_env("TS_FIRST_JOBID", get_jobids_DB())); @@ -351,7 +351,7 @@ static void server_loop(int ls) { } } - for (i = 0; i < nconnections; ++i) + for (i = 0; i < nconnections; ++i) { if (FD_ISSET(client_cs[i].socket, &readset)) { enum Break b; b = client_read(i); @@ -372,11 +372,14 @@ static void server_loop(int ls) { } */ } + } // nconnections + /* This will return firstjob->jobid or -1 */ newjob = next_run_job(); // printf("end of next_run, newjob = %d\n", newjob); if (newjob != -1) { + check_pause(); int conn, awaken_job; conn = get_conn_of_jobid(newjob); /* This next marks the firstjob state to RUNNING */ @@ -399,6 +402,8 @@ static void server_loop(int ls) { static void end_server(int ls) { close(ls); unlink(path); + close_sqlite(); + free_pause_array(); /* This comes from the parent, in the fork after server_main. * This is the last use of path in this process.*/ free(path); From 43809a225c25c202f848a0e46668c191963c5167 Mon Sep 17 00:00:00 2001 From: kylin Date: Sat, 22 Apr 2023 09:25:45 +0800 Subject: [PATCH 68/91] new versions --- Makefile | 4 +- default.inc | 5 +++ jobs.c | 24 ++++++++++ main.c | 1 - server_start.c | 118 +++++++++++++++++++++++-------------------------- user.txt | 2 +- 6 files changed, 87 insertions(+), 67 deletions(-) diff --git a/Makefile b/Makefile index 564e22d..a8ae1df 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ PREFIX?=/usr/local PREFIX_LOCAL=~ -GLIBCFLAGS=-D_XOPEN_SOURCE=500 -D__STRICT_ANSI__ +GLIBCFLAGS=#-D_XOPEN_SOURCE=500 -D__STRICT_ANSI__ CPPFLAGS+=$(GLIBCFLAGS) -CFLAGS?=-pedantic -ansi -Wall -g -O0 -std=gnu11 -DTASKSET +CFLAGS?=-pedantic -ansi -Wall -g -std=gnu11 -DTASKSET -DSOUND OBJECTS=main.o \ server.o \ server_start.o \ diff --git a/default.inc b/default.inc index 83a1426..21a3394 100644 --- a/default.inc +++ b/default.inc @@ -1,3 +1,8 @@ #define DEFAULT_USER_PATH "/home/kylin/task-spooler/user.txt" #define DEFAULT_LOG_PATH "/home/kylin/task-spooler/log.txt" #define DEFAULT_SQLITE_PATH "/home/kylin/task-spooler/task-spooler.db" +#define DEFAULT_NOTIFICATION_SOUND "/home/kylin/task-spooler/notifications-sound.wav" +#define DEFAULT_ERROR_SOUND "/home/kylin/task-spooler/error.wav" +#define DEFAULT_PULSE_SERVER "unix:/mnt/wslg/PulseServer" + + diff --git a/jobs.c b/jobs.c index 3d36863..93a7272 100644 --- a/jobs.c +++ b/jobs.c @@ -19,6 +19,7 @@ #include "main.h" #include "user.h" #include "cjson/cJSON.h" +#include "default.inc" /* The list will access them */ int busy_slots = 0; @@ -47,6 +48,7 @@ static char buff[256]; int max_jobs; static struct Job *get_job(int jobid); +static int fork_cmd(int UID, const char* path, const char* cmd); void notify_errorlevel(struct Job *p); @@ -55,6 +57,27 @@ void s_set_jobids(int i) { set_jobids_DB(i); } +static void sound_notify(struct Job* p) { + #ifdef SOUND + float real_ms = p->result.real_ms; + if (real_ms == 0.0) { + real_ms = p->info.end_time.tv_sec - p->info.start_time.tv_sec; + real_ms += 1e-6 * (p->info.end_time.tv_usec - p->info.start_time.tv_usec); + } + // skip the short task + printf("real_ms = %g\n", real_ms); + if (real_ms < 5) return; + char cmd[256]; + if (p->result.errorlevel == 0) { + snprintf(cmd, 255, "paplay -p \"%s\" -s %s", DEFAULT_NOTIFICATION_SOUND, DEFAULT_PULSE_SERVER); + } else { + snprintf(cmd, 255, "paplay -p \"%s\" -s %s", DEFAULT_ERROR_SOUND, DEFAULT_PULSE_SERVER); + } + printf("%s\n", cmd); + fork_cmd(user_UID[p->ts_UID], NULL, cmd); + #endif +} + static void destroy_job(struct Job *p) { if (p != NULL) { free(p->notify_errorlevel_to); @@ -1288,6 +1311,7 @@ static void new_finished_job(struct Job *j) { #ifdef TASKSET unlock_core_by_job(j); #endif + sound_notify(j); } static int job_is_in_state(int jobid, enum Jobstate state) { diff --git a/main.c b/main.c index bd8e7a1..a940892 100644 --- a/main.c +++ b/main.c @@ -680,7 +680,6 @@ int main(int argc, char **argv) { user_locker = -1; client_uid = getuid(); // printf("client_uid = %u\n", client_uid); - init_version(); get_terminal_width(); process_type = CLIENT; diff --git a/server_start.c b/server_start.c index 8fc5cd5..95be456 100644 --- a/server_start.c +++ b/server_start.c @@ -48,69 +48,61 @@ static void setup_kill_sh() { printf("Cannot create `kill_ppide.sh` file at %s\n", path); exit(0); } - fprintf(f, R"(#!/bin/bash - -# getting children generally resolves nicely at some point -get_child() { - echo $(pgrep -laP $1 | awk '{print $1}') -} - -get_children() { - __RET=$(get_child $1) - __CHILDREN= - while [ -n "$__RET" ]; do - __CHILDREN+="$__RET " - __RET=$(get_child $__RET) - done - - __CHILDREN=$(echo "${__CHILDREN}" | xargs | sort) - - echo "${__CHILDREN} $1" -} - -if [ 1 -gt $# ]; -then - echo "not input PID" - exit 1 -fi - -owner=`ps -o user= -p $1` -if [ -z "$owner" ]; -then - # echo "not a valid PID" - exit 1 -fi -pids=`get_children $1` - -user=`whoami` - -extra="" -if [[ "$owner" != "$user" ]]; then - extra="sudo" -fi - -if [ -z "$3" ] -then - if [ -z "$2" ] - then - for pid in ${pids}; - do - ${extra} echo ${pid} - done - else - for pid in ${pids}; - do - ${extra} $2 ${pid} - done - fi -else - for pid in ${pids}; - do - ${extra} $2 ${pid} - ${extra} $3 ${pid} - done -fi -)"); + const char* script = "#!/bin/bash\n\n" + "# getting children generally resolves nicely at some point\n" + "get_child() {\n" + " echo $(pgrep -laP $1 | awk '{print $1}')\n" + "}\n\n" + "get_children() {\n" + " __RET=$(get_child $1)\n" + " __CHILDREN=\n" + " while [ -n \"$__RET\" ]; do\n" + " __CHILDREN+=\"$__RET \"\n" + " __RET=$(get_child $__RET)\n" + " done\n\n" + " __CHILDREN=$(echo \"${__CHILDREN}\" | xargs | sort)\n\n" + " echo \"${__CHILDREN} $1\"\n" + "}\n\n" + "if [ 1 -gt $# ]; \n" + "then\n" + " echo \"not input PID\"\n" + " exit 1\n" + "fi\n\n" + "owner=`ps -o user= -p $1`\n" + "if [ -z \"$owner\" ]; \n" + "then\n" + " // echo \"not a valid PID\"\n" + " exit 1\n" + "fi\n" + "pids=`get_children $1`\n\n" + "user=`whoami`\n\n" + "extra=\"\"\n" + "if [[ \"$owner\" != \"$user\" ]]; then\n" + " extra=\"sudo\"\n" + "fi\n\n" + "if [ -z \"$3\" ]\n" + "then\n" + " if [ -z \"$2\" ]\n" + " then\n" + " for pid in ${pids};\n" + " do\n" + " ${extra} echo ${pid}\n" + " done\n" + " else\n" + " for pid in ${pids};\n" + " do\n" + " ${extra} $2 ${pid}\n" + " done\n" + " fi\n" + "else\n" + " for pid in ${pids};\n" + " do\n" + " ${extra} $2 ${pid}\n" + " ${extra} $3 ${pid}\n" + " done\n" + "fi;\n"; + + fprintf(f, "%s", script); fclose(f); printf(" Kill_PPID.sh at `%s`\n\n", path); diff --git a/user.txt b/user.txt index 0a4c698..8896eb7 100644 --- a/user.txt +++ b/user.txt @@ -1,5 +1,5 @@ # 1231 # comments -TS_SLOTS = 4 # environment variable +TS_SLOTS = 16 # environment variable TS_FIRST_JOBID = 2000 # environment variable # uid name slots 1000 Kylin 10 From 92177ebd796878911b5ab6ddb0d4fdf68be40ee7 Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Mon, 9 Oct 2023 01:18:34 +0800 Subject: [PATCH 69/91] add sstmp.c to send the email --- Makefile | 3 ++- client.c | 13 ++++++++++++- execute.c | 12 ++++++++---- jobs.c | 38 ++++++++++++++++++++++++++++++++++++-- list.c | 13 +++++++++++-- main.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++----- main.h | 5 +++++ 7 files changed, 121 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index a8ae1df..66ea65a 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ + PREFIX?=/usr/local PREFIX_LOCAL=~ GLIBCFLAGS=#-D_XOPEN_SOURCE=500 -D__STRICT_ANSI__ CPPFLAGS+=$(GLIBCFLAGS) -CFLAGS?=-pedantic -ansi -Wall -g -std=gnu11 -DTASKSET -DSOUND +CFLAGS?=-pedantic -ansi -Wall -g -std=gnu11 -DTASKSET -DSOUND -fcommon OBJECTS=main.o \ server.o \ server_start.o \ diff --git a/client.c b/client.c index d2ec8f1..57d551c 100644 --- a/client.c +++ b/client.c @@ -56,7 +56,7 @@ char *charArray_string(int num, char** array) { } void c_new_job() { - + // printf("new _job \n"); struct Msg m = default_msg(); char *new_command, path[1024]; char *myenv; @@ -78,10 +78,18 @@ void c_new_job() { m.u.newjob.env_size = strlen(myenv) + 1; /* add null */ else m.u.newjob.env_size = 0; + if (command_line.label) m.u.newjob.label_size = strlen(command_line.label) + 1; /* add null */ else m.u.newjob.label_size = 0; + + if (command_line.email) { + // printf("send email to %s\n", command_line.email); + m.u.newjob.email_size = strlen(command_line.email) + 1; /* add null */ + } else + m.u.newjob.email_size = 0; + m.u.newjob.store_output = command_line.store_output; m.u.newjob.depend_on_size = command_line.depend_on_size; m.u.newjob.should_keep_finished = command_line.should_keep_finished; @@ -111,6 +119,9 @@ void c_new_job() { /* Send the label */ send_bytes(server_socket, command_line.label, m.u.newjob.label_size); + /* Send the label */ + send_bytes(server_socket, command_line.email, m.u.newjob.email_size); + /* Send the environment */ send_bytes(server_socket, myenv, m.u.newjob.env_size); diff --git a/execute.c b/execute.c index 822c8a5..74236ac 100644 --- a/execute.c +++ b/execute.c @@ -280,9 +280,13 @@ static void run_child(int fd_send_filename, const char *tmpdir, int jobid) { // int len_outfname = 1 + strlen(label) + strlen(".XXXXXX") + 1; int len_outfname = 3 + strlen(label) + strlen(jobid_str); - outfname = malloc(len_outfname); - - snprintf(outfname, len_outfname, "/%s.%d", label, jobid); + if (command_line.outfile == NULL) { + outfname = malloc(len_outfname); + snprintf(outfname, len_outfname, "/%s.%d", label, jobid); + } else { + outfname = command_line.outfile; + tmpdir = ""; + } if (command_line.store_output) { /* Prepare path */ @@ -291,7 +295,7 @@ static void run_child(int fd_send_filename, const char *tmpdir, int jobid) { // if (tmpdir == NULL) // tmpdir = "/tmp"; - lname = strlen(outfname) + strlen(tmpdir) + 1 /* \0 */; + lname = strlen(outfname) + strlen(tmpdir) + 5 /* \0 */; outfname_full = (char *)malloc(lname); strcpy(outfname_full, tmpdir); diff --git a/jobs.c b/jobs.c index 93a7272..fc951ea 100644 --- a/jobs.c +++ b/jobs.c @@ -24,6 +24,8 @@ /* The list will access them */ int busy_slots = 0; int max_slots = 1; +float sstmp_skip_ms = 1; // 200000; // skip task smaller than 200 s +const char email_sender[] = "kylincaster@foxmail.com"; struct Notify { int socket; @@ -57,6 +59,24 @@ void s_set_jobids(int i) { set_jobids_DB(i); } + + +static void send_mail_via_ssmtp(struct Job* p) { + float real_ms = p->result.real_ms; + if (real_ms == 0.0) { + real_ms = p->info.end_time.tv_sec - p->info.start_time.tv_sec; + real_ms += 1e-6 * (p->info.end_time.tv_usec - p->info.start_time.tv_usec); + } + // skip the short task + if (real_ms < sstmp_skip_ms || p->email == NULL) return; + + const char* unit = time_rep(&real_ms); + char cmd[1024]; + snprintf(cmd, 1023, "echo \"Subject: %s[%d] n_core: %d, Elsp %.3f %s from MSI\nFrom: TS<%s>\nTo: %s\n\n\n Cmd: %s exit-code:%d\n Output: %s\" | ssmtp %s", + p->label, p->jobid, p->num_slots, real_ms, unit, p->email, email_sender, p->command + p->command_strip, p->result.signal, p->output_filename, p->email); + fork_cmd(root_UID, NULL, cmd); +} + static void sound_notify(struct Job* p) { #ifdef SOUND float real_ms = p->result.real_ms; @@ -65,7 +85,6 @@ static void sound_notify(struct Job* p) { real_ms += 1e-6 * (p->info.end_time.tv_usec - p->info.start_time.tv_usec); } // skip the short task - printf("real_ms = %g\n", real_ms); if (real_ms < 5) return; char cmd[256]; if (p->result.errorlevel == 0) { @@ -873,6 +892,7 @@ static struct Job *newjobptr() { p = p->next; p->next = (struct Job *)calloc(sizeof(struct Job), sizeof(char)); + /* p->next->next = 0; p->next->output_filename = 0; @@ -1113,7 +1133,7 @@ int s_newjob(int s, struct Msg *m, int ts_UID) { } /* load the label */ - p->label = 0; + p->label = NULL; if (m->u.newjob.label_size > 0) { char *ptr; ptr = (char *)malloc(m->u.newjob.label_size); @@ -1126,6 +1146,19 @@ int s_newjob(int s, struct Msg *m, int ts_UID) { p->label = ptr; } + p->email = NULL; + if (m->u.newjob.email_size > 0) { + char *ptr; + ptr = (char *)malloc(m->u.newjob.email_size); + if (ptr == 0) + error("Cannot allocate memory in s_newjob email_size(%i)", + m->u.newjob.email_size); + res = recv_bytes(s, ptr, m->u.newjob.email_size); + if (res == -1) + error("wrong bytes received"); + p->email = ptr; + } + /* load the info */ if (m->u.newjob.env_size > 0) { char *ptr; @@ -1312,6 +1345,7 @@ static void new_finished_job(struct Job *j) { unlock_core_by_job(j); #endif sound_notify(j); + send_mail_via_ssmtp(j); } static int job_is_in_state(int jobid, enum Jobstate state) { diff --git a/list.c b/list.c index b851b0a..1c3a1f6 100644 --- a/list.c +++ b/list.c @@ -93,6 +93,14 @@ char *joblist_headers() { static int max(int a, int b) { return a > b ? a : b; } +static const char* jstate2string_result(const struct Job* p) { + if (p->result.errorlevel != 0 || p->result.signal != 0 || p->result.died_by_signal != 0) { + return "failed"; + } else { + return jstate2string(p->state); + } +} + static const char *ofilename_shown(const struct Job *p) { const char *output_filename; @@ -216,7 +224,7 @@ static char *print_result(const struct Job *p) { const char *unit = time_rep(&real_ms); int cmd_len; - jobstate = jstate2string(p->state); + jobstate = jstate2string_result(p); output_filename = ofilename_shown(p); char *uname = user_name[p->ts_UID]; @@ -331,6 +339,7 @@ static char *plainprint_noresult(const struct Job *p) { return line; } + static char *plainprint_result(const struct Job *p) { const char *jobstate; int maxlen; @@ -346,7 +355,7 @@ static char *plainprint_result(const struct Job *p) { const char *unit = time_rep(&real_ms); - jobstate = jstate2string(p->state); + jobstate = jstate2string_result(p); output_filename = ofilename_shown(p); maxlen = 4 + 1 + 10 + 1 + 20 + 1 + 8 + 1 + 25 + 1 + strlen(p->command) + diff --git a/main.c b/main.c index a940892..6ea9464 100644 --- a/main.c +++ b/main.c @@ -13,6 +13,7 @@ #include #include #include +#include // time() #include "main.h" #include "version.h" @@ -47,6 +48,7 @@ static void default_command_line() { command_line.send_output_by_mail = 0; command_line.linux_cmd = NULL; command_line.label = NULL; + command_line.email = NULL; command_line.depend_on_size = 0; command_line.depend_on = NULL; /* -1 means depend on previous */ command_line.max_slots = 1; @@ -78,6 +80,40 @@ void get_command(int index, int argc, char **argv) { command_line.command.num = argc - index; } +char* get_tmp() { + const char* tmpFolder = getenv("TMPDIR"); + if (tmpFolder == NULL) { + tmpFolder = "/tmp/"; + } + const char* fileNameFormat = "ts_out.%06d"; + const int folderLength = strlen(tmpFolder); + const int maxFileNameLength = folderLength + strlen(fileNameFormat) + 10; + + char* fileName = (char*)malloc(maxFileNameLength * sizeof(char)); + char* fileName2 = (char*)malloc(maxFileNameLength * sizeof(char)); + + if (fileName == NULL || fileName2 == NULL) { + fprintf(stderr, "Memory allocation failed.\n"); + return NULL; + } + + srand(time(NULL)); + uint randomNumber1 = rand() % 10000; + uint randomNumber2 = rand() % 10000; + uint randomNumber3 = rand() % 10000; + + const int randomRange = 100000; + uint randomOffset = randomNumber1 * randomRange * randomRange + randomNumber2 * randomRange + randomNumber3; + + int finalRandomNumber = randomOffset % randomRange; + + snprintf(fileName, maxFileNameLength, "%s/%s", tmpFolder, fileNameFormat); + snprintf(fileName2, maxFileNameLength, fileName, finalRandomNumber); + printf("save to %s\n", fileName2); + free(fileName); + return fileName2; +} + static int get_two_jobs(const char *str, int *j1, int *j2) { char tmp[50]; char *tmp2; @@ -130,6 +166,7 @@ static struct option longOptions[] = { {"lock-ts", no_argument, NULL, 0}, {"unlock-ts", no_argument, NULL, 0}, {"daemon", no_argument, NULL, 0}, + {"tmp", no_argument, NULL, 0}, {"relink", required_argument, NULL, 0}, {"jobid", required_argument, NULL, 'J'}, {"stime", required_argument, NULL, 0}, @@ -144,7 +181,7 @@ void parse_opts(int argc, char **argv) { /* Parse options */ while (1) { c = getopt_long(argc, argv, - ":AXRTVhKzClnfmBE:a:F:t:c:o:p:w:k:r:u:s:U:qi:N:J:L:dS:D:W:O:M:", + ":AXRTVhKzClnfBE:a:F:t:c:o:p:w:k:r:u:s:U:qi:N:J:m:L:dS:D:W:O:M:", longOptions, &optionIdx); if (c == -1) @@ -156,6 +193,8 @@ void parse_opts(int argc, char **argv) { command_line.request = c_GET_LOGDIR; } else if (strcmp(longOptions[optionIdx].name, "daemon") == 0) { command_line.request = c_DAEMON; + } else if (strcmp(longOptions[optionIdx].name, "tmp") == 0) { + command_line.outfile = get_tmp(); } else if (strcmp(longOptions[optionIdx].name, "check_daemon") == 0) { command_line.request = c_CHECK_DAEMON; command_line.need_server = 0; @@ -274,7 +313,7 @@ void parse_opts(int argc, char **argv) { command_line.should_go_background = 0; break; case 'm': - command_line.send_output_by_mail = 1; + command_line.email = optarg; break; case 't': command_line.request = c_TAIL; @@ -485,12 +524,14 @@ void parse_opts(int argc, char **argv) { if (!command_line.store_output && !command_line.should_go_background) command_line.should_keep_finished = 0; + /* if (command_line.send_output_by_mail && ((!command_line.store_output) || command_line.gzip)) { fprintf(stderr, "For e-mail, you should store the output (not through gzip)\n"); exit(-1); } + */ } static void fill_first_3_handles() { @@ -533,8 +574,8 @@ static void print_help(const char *cmd) { printf("usage: %s [action] [-ngfmdE] [-L ] [-D ] [cmd...]\n", cmd); printf("Env vars:\n"); printf(" TS_SOCKET the path to the unix socket used by the ts command.\n"); - printf(" TS_MAILTO where to mail the result (on -m). Local user by " - "default.\n"); + // printf(" TS_MAILTO where to mail the result (or set by -m). Local user by " + // "default.\n"); printf(" TS_MAXFINISHED maximum finished jobs in the queue.\n"); printf(" TS_MAXCONN maximum number of ts connections at once.\n"); printf(" TS_ONFINISH binary called on job end (passes jobid, error, " @@ -582,6 +623,7 @@ static void print_help(const char *cmd) { " --serialize || -M [format] serialize the job list to the specified format. Choices: {default, json, tab}.\n"); printf(" --daemon Run the server as daemon by Root " "only.\n"); + printf(" --tmp save the logfile to tmp folder\n"); printf(" --pause [jobid] hold on a task.\n"); printf(" --rerun [jobid] rerun a paused task.\n"); printf(" --lock Locker the server (Timeout: 30 " @@ -641,7 +683,7 @@ static void print_help(const char *cmd) { printf(" -O Set name of the log file (without any path).\n"); printf(" -z gzip the stored output (if not -n).\n"); printf(" -f don't fork into background.\n"); - printf(" -m send the output by e-mail (uses sendmail).\n"); + printf(" -m send the output by e-mail (uses ssmtp).\n"); printf(" -d the job will be run after the last job ends.\n"); printf( " -D the job will be run after the job of given IDs ends.\n"); diff --git a/main.h b/main.h index 2d113df..3e4bff8 100644 --- a/main.h +++ b/main.h @@ -134,6 +134,7 @@ struct CommandLine { } command; char *linux_cmd; char *label; + char *email; char *logfile; char *outfile; int num_slots; /* Slots for the job to use. Default 1 */ @@ -176,6 +177,7 @@ struct Msg { int store_output; int should_keep_finished; int label_size; + int email_size; int env_size; int depend_on_size; int wait_enqueuing; @@ -243,6 +245,7 @@ struct Job { int notify_errorlevel_to_size; int dependency_errorlevel; char *label; + char *email; struct Procinfo info; int num_slots; int num_allocated; @@ -618,3 +621,5 @@ void init_taskset(); int set_task_cores(struct Job* p, const char* extra); void unlock_core_by_job(struct Job* p); +/* sstmp.c */ +void send_mail_via_sstmp(struct Job* p); \ No newline at end of file From 44122ea567868a7f532bb489e73523c9bce64cfb Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Mon, 9 Oct 2023 01:48:09 +0800 Subject: [PATCH 70/91] add sstmp to send email --- Makefile | 2 +- default.inc | 4 ++-- jobs.c | 28 ++++++++++++++++++++------ main.h | 4 +--- server.c | 1 + sqlite.c | 57 ++++++++++++++++++++++++++++++++--------------------- version.h | 2 +- 7 files changed, 63 insertions(+), 35 deletions(-) diff --git a/Makefile b/Makefile index 66ea65a..d1bd839 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ PREFIX?=/usr/local PREFIX_LOCAL=~ GLIBCFLAGS=#-D_XOPEN_SOURCE=500 -D__STRICT_ANSI__ CPPFLAGS+=$(GLIBCFLAGS) -CFLAGS?=-pedantic -ansi -Wall -g -std=gnu11 -DTASKSET -DSOUND -fcommon +CFLAGS?=-pedantic -ansi -Wall -g -std=gnu11 -DTASKSET -DSOUND -fcommon -Wno-format-truncation OBJECTS=main.o \ server.o \ server_start.o \ diff --git a/default.inc b/default.inc index 21a3394..25ca4e8 100644 --- a/default.inc +++ b/default.inc @@ -4,5 +4,5 @@ #define DEFAULT_NOTIFICATION_SOUND "/home/kylin/task-spooler/notifications-sound.wav" #define DEFAULT_ERROR_SOUND "/home/kylin/task-spooler/error.wav" #define DEFAULT_PULSE_SERVER "unix:/mnt/wslg/PulseServer" - - +#define DEFAULT_EMAIL_SENDER "kylincaster@foxmail.com" +#define DEFAULT_EMAIL_TIME 200000 \ No newline at end of file diff --git a/jobs.c b/jobs.c index fc951ea..1bbaf6b 100644 --- a/jobs.c +++ b/jobs.c @@ -24,8 +24,9 @@ /* The list will access them */ int busy_slots = 0; int max_slots = 1; -float sstmp_skip_ms = 1; // 200000; // skip task smaller than 200 s -const char email_sender[] = "kylincaster@foxmail.com"; +float sstmp_skip_ms = DEFAULT_EMAIL_TIME; // 200000; // skip task smaller than 200 s + +char* email_sender; struct Notify { int socket; @@ -59,7 +60,20 @@ void s_set_jobids(int i) { set_jobids_DB(i); } - +void setup_ssmtp() { + email_sender = getenv("TS_MAIL_FROM"); + if (email_sender == NULL) { + email_sender = DEFAULT_EMAIL_SENDER; + } + char* time_s = getenv("TS_MAIL_TIME"); + if (time_s != NULL) { + float time_sec; + int ret = sscanf(time_s, "%f", &time_sec); + if (ret == 1) { + sstmp_skip_ms = time_sec * 1000; + } + } +} static void send_mail_via_ssmtp(struct Job* p) { float real_ms = p->result.real_ms; @@ -439,7 +453,7 @@ int s_check_relink(int s, int pid, int ts_UID) { } else if (ts_UID == job_tsUID) { ; } else { - sprintf(buff, " Error: PID [%i] is owned by [%d] `%s` not the user [%d] `%s`\n", + snprintf(buff, 255, " Error: PID [%i] is owned by [%d] `%150s` not the user [%d] `%s`\n", pid, user_UID[job_tsUID], user_name[job_tsUID], user_UID[ts_UID], user_name[ts_UID]); send_list_line(s, buff); @@ -1712,6 +1726,9 @@ void s_job_info(int s, int jobid) { fd_nprintf(s, 100, "Output: %s\n", p->output_filename); fd_nprintf(s, 100, "Enqueue time: %s", ctime(&p->info.enqueue_time.tv_sec)); fd_nprintf(s, 100, "Start time: %s", ctime(&p->info.start_time.tv_sec)); + if (p->email) { + fd_nprintf(s, 100, "Email: %s\n", p->email); + } if (p->state == RUNNING) { t = pinfo_time_until_now(&p->info); } else if (p->state == FINISHED) { @@ -1720,7 +1737,6 @@ void s_job_info(int s, int jobid) { } const char *unit = time_rep(&t); fd_nprintf(s, 100, "Time running: %.4f %s\n", t, unit); - // fd_nprintf(s, 100, "\n"); } @@ -1773,7 +1789,7 @@ void s_cont_user(int s, int ts_UID) { p = p->next; } - snprintf(buff, 255, "Resume user: [%d] %s\n", user_UID[ts_UID], user_name[ts_UID]); + snprintf(buff, 255, "Resume user: [%d] %199s\n", user_UID[ts_UID], user_name[ts_UID]); send_list_line(s, buff); } diff --git a/main.h b/main.h index 3e4bff8..9b07b3c 100644 --- a/main.h +++ b/main.h @@ -583,6 +583,7 @@ void init_pause(); void check_pause(); void free_pause_array(); struct Job *findjob(int jobid); +void setup_ssmtp(); /* client.c */ void c_list_jobs_all(); @@ -620,6 +621,3 @@ char* insert_chars(int pos, const char* input, const char* c); void init_taskset(); int set_task_cores(struct Job* p, const char* extra); void unlock_core_by_job(struct Job* p); - -/* sstmp.c */ -void send_mail_via_sstmp(struct Job* p); \ No newline at end of file diff --git a/server.c b/server.c index 5de643a..bacd900 100644 --- a/server.c +++ b/server.c @@ -266,6 +266,7 @@ void server_main(int notify_fd, char *_path) { // jobDB_Jobs = NULL; init_taskset(); set_server_logfile(); + setup_ssmtp(); // int jobid = read_first_jobid_from_logfile(logfile_path); read_user_file(get_user_path()); set_socket_model(_path); diff --git a/sqlite.c b/sqlite.c index 957fcd0..f16a596 100644 --- a/sqlite.c +++ b/sqlite.c @@ -97,6 +97,7 @@ int open_sqlite() { "notify_errorlevel_to_size INT NOT NULL," \ "dependency_errorlevel INT NOT NULL," \ "label TEXT NOT NULL," \ + "email TEXT NOT NULL," \ "num_slots INT NOT NULL, " \ "errorlevel INT NOT NULL, died_by_signal INT NOT NULL, signal INT NOT NULL, "\ "user_ms FLOAT NOT NULL, system_ms FLOAT NOT NULL, real_ms FLOAT NOT NULL, skipped INT NOT NULL, " \ @@ -129,6 +130,7 @@ int open_sqlite() { "notify_errorlevel_to_size INT NOT NULL," \ "dependency_errorlevel INT NOT NULL," \ "label TEXT NOT NULL," \ + "email TEXT NOT NULL," \ "num_slots INT NOT NULL, " \ "errorlevel INT NOT NULL, died_by_signal INT NOT NULL, signal INT NOT NULL, "\ "user_ms FLOAT NOT NULL, system_ms FLOAT NOT NULL, real_ms FLOAT NOT NULL, skipped INT NOT NULL, " \ @@ -200,6 +202,8 @@ static int edit_DB(struct Job* job, const char* table, const char* action) { struct Result* result = &(job->result); struct Procinfo* info= &(job->info); const char* label = job->label == NULL ? "(..)" : job->label; + const char* email = job->email == NULL ? "(..)" : job->email; + char sql[1024]; int order_id = get_order_id(job->jobid); @@ -210,13 +214,13 @@ static int edit_DB(struct Job* job, const char* table, const char* action) { char* notify_errorlevel_to = ints_to_chars(job->notify_errorlevel_to_size, job->notify_errorlevel_to, ","); sprintf(sql, "%s INTO %s (jobid, command, state, output_filename, store_output, pid, ts_UID, should_keep_finished, depend_on, depend_on_size," \ - "notify_errorlevel_to, notify_errorlevel_to_size, dependency_errorlevel,label,num_slots,errorlevel,died_by_signal," \ + "notify_errorlevel_to, notify_errorlevel_to_size, dependency_errorlevel,label,email,num_slots,errorlevel,died_by_signal," \ "signal,user_ms,system_ms,real_ms,skipped," \ "ptr,nchars,allocchars," \ "enqueue_time,start_time,end_time," \ "enqueue_time_ms,start_time_ms,end_time_ms, " \ "order_id, command_strip, work_dir)" \ - "VALUES (%d,'%s',%d,'%s',%d,%d,%d,%d,'%s',%d,'%s',%d,%d,'%s',%d," \ + "VALUES (%d,'%s',%d,'%s',%d,%d,%d,%d,'%s',%d,'%s',%d,%d,'%s','%s',%d," \ "%d,%d,%d,%f,%f,%f,%d,"\ "'%s',%d,%d,'%ld','%ld','%ld','%ld','%ld','%ld', " \ "%d, %d,'%s');", @@ -236,6 +240,7 @@ static int edit_DB(struct Job* job, const char* table, const char* action) { job->notify_errorlevel_to_size, job->dependency_errorlevel, label, + email, job->num_slots, result->errorlevel, result->died_by_signal, result->signal, result->user_ms, result->system_ms, result->real_ms, result->skipped, @@ -426,34 +431,42 @@ struct Job* read_DB(int jobid, const char* table) { strcpy(sql, (const char*) sqlite3_column_text(stmt, 13)); job->label = (char*) malloc(sizeof(char) * (strlen(sql)+1)); strcpy(job->label, sql); - - job->num_slots = sqlite3_column_int(stmt, 14); - result->errorlevel = sqlite3_column_int(stmt, 15); - result->died_by_signal = sqlite3_column_int(stmt, 16); - result->signal = sqlite3_column_int(stmt, 17); - result->user_ms = (float)sqlite3_column_double(stmt, 18); - result->system_ms = (float)sqlite3_column_double(stmt, 19); - result->real_ms = (float)sqlite3_column_double(stmt, 20); - result->skipped = sqlite3_column_int(stmt, 21); + strcpy(sql, (const char*) sqlite3_column_text(stmt, 14)); + job->email = (char*) malloc(sizeof(char) * (strlen(sql)+1)); + if (strcmp(sql, "(..)") != 0) { + strcpy(job->email, sql); + } else { + job->email = NULL; + } + + job->num_slots = sqlite3_column_int(stmt, 15); + + result->errorlevel = sqlite3_column_int(stmt, 16); + result->died_by_signal = sqlite3_column_int(stmt, 17); + result->signal = sqlite3_column_int(stmt, 18); + result->user_ms = (float)sqlite3_column_double(stmt, 19); + result->system_ms = (float)sqlite3_column_double(stmt, 20); + result->real_ms = (float)sqlite3_column_double(stmt, 21); + result->skipped = sqlite3_column_int(stmt, 22); - strcpy(sql, (const char*) sqlite3_column_text(stmt, 22)); + strcpy(sql, (const char*) sqlite3_column_text(stmt, 23)); info->ptr = (char*) malloc(sizeof(char) * (strlen(sql)+1)); strcpy(info->ptr, sql); - info->nchars=sqlite3_column_bytes(stmt,23)/sizeof(char); - info->allocchars=sqlite3_column_bytes(stmt,24)/sizeof(char); + info->nchars=sqlite3_column_bytes(stmt,24)/sizeof(char); + info->allocchars=sqlite3_column_bytes(stmt,25)/sizeof(char); - info->enqueue_time.tv_sec=sqlite3_column_int64(stmt,25); - info->start_time.tv_sec=sqlite3_column_int64(stmt,26); - info->end_time.tv_sec=sqlite3_column_int64(stmt,27); + info->enqueue_time.tv_sec=sqlite3_column_int64(stmt,26); + info->start_time.tv_sec=sqlite3_column_int64(stmt,27); + info->end_time.tv_sec=sqlite3_column_int64(stmt,28); - info->enqueue_time.tv_usec=sqlite3_column_int64(stmt,28); - info->start_time.tv_usec=sqlite3_column_int64(stmt,29); - info->end_time.tv_usec=sqlite3_column_int64(stmt,30); - job->command_strip=sqlite3_column_int(stmt, 32); + info->enqueue_time.tv_usec=sqlite3_column_int64(stmt,29); + info->start_time.tv_usec=sqlite3_column_int64(stmt,30); + info->end_time.tv_usec=sqlite3_column_int64(stmt,31); + job->command_strip=sqlite3_column_int(stmt, 33); - strcpy(sql, (const char*) sqlite3_column_text(stmt, 33)); + strcpy(sql, (const char*) sqlite3_column_text(stmt, 34)); job->work_dir = (char*) malloc(sizeof(char) * (strlen(sql)+1)); strcpy(job->work_dir, sql); diff --git a/version.h b/version.h index 0037296..6b987bb 100644 --- a/version.h +++ b/version.h @@ -6,7 +6,7 @@ #define TASK_SPOOLER_VERSION_H #ifndef TS_VERSION -#define TS_VERSION 2.0.0 +#define TS_VERSION 2.1.0 #endif /* from https://github.com/LLNL/lbann/issues/117 From 7674c9c34a43e7b59395f18b9466a799c4b82783 Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Mon, 9 Oct 2023 09:32:41 +0800 Subject: [PATCH 71/91] add help for ssmtp email --- main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/main.c b/main.c index 6ea9464..d12c249 100644 --- a/main.c +++ b/main.c @@ -17,6 +17,7 @@ #include "main.h" #include "version.h" +#include "default.inc" int client_uid; extern char *optarg; @@ -574,8 +575,8 @@ static void print_help(const char *cmd) { printf("usage: %s [action] [-ngfmdE] [-L ] [-D ] [cmd...]\n", cmd); printf("Env vars:\n"); printf(" TS_SOCKET the path to the unix socket used by the ts command.\n"); - // printf(" TS_MAILTO where to mail the result (or set by -m). Local user by " - // "default.\n"); + printf(" TS_MAIL_FROM who send the result mail, default (%s)\n", DEFAULT_EMAIL_SENDER); + printf(" TS_MAIL_TIME the duration criterion to send a email, default (%.3d sec)\n", DEFAULT_EMAIL_TIME / 1000); printf(" TS_MAXFINISHED maximum finished jobs in the queue.\n"); printf(" TS_MAXCONN maximum number of ts connections at once.\n"); printf(" TS_ONFINISH binary called on job end (passes jobid, error, " From aefb648c9aae9b839c1cd0400353ed3d25847cd8 Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Mon, 9 Oct 2023 09:50:24 +0800 Subject: [PATCH 72/91] fix bug for the email_time --- default.inc | 2 +- jobs.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/default.inc b/default.inc index 25ca4e8..11f5534 100644 --- a/default.inc +++ b/default.inc @@ -5,4 +5,4 @@ #define DEFAULT_ERROR_SOUND "/home/kylin/task-spooler/error.wav" #define DEFAULT_PULSE_SERVER "unix:/mnt/wslg/PulseServer" #define DEFAULT_EMAIL_SENDER "kylincaster@foxmail.com" -#define DEFAULT_EMAIL_TIME 200000 \ No newline at end of file +#define DEFAULT_EMAIL_TIME 30 \ No newline at end of file diff --git a/jobs.c b/jobs.c index 1bbaf6b..3ef3dbe 100644 --- a/jobs.c +++ b/jobs.c @@ -70,13 +70,13 @@ void setup_ssmtp() { float time_sec; int ret = sscanf(time_s, "%f", &time_sec); if (ret == 1) { - sstmp_skip_ms = time_sec * 1000; + sstmp_skip_ms = time_sec; } } } static void send_mail_via_ssmtp(struct Job* p) { - float real_ms = p->result.real_ms; + float real_ms = p->result.real_ms; // units in second if (real_ms == 0.0) { real_ms = p->info.end_time.tv_sec - p->info.start_time.tv_sec; real_ms += 1e-6 * (p->info.end_time.tv_usec - p->info.start_time.tv_usec); From de9c945538fe735ac2e034d9e89a2c2d2cfa0241 Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Mon, 9 Oct 2023 10:12:57 +0800 Subject: [PATCH 73/91] fix bug for the email_time --- default.inc | 2 +- main.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/default.inc b/default.inc index 11f5534..16f16a7 100644 --- a/default.inc +++ b/default.inc @@ -5,4 +5,4 @@ #define DEFAULT_ERROR_SOUND "/home/kylin/task-spooler/error.wav" #define DEFAULT_PULSE_SERVER "unix:/mnt/wslg/PulseServer" #define DEFAULT_EMAIL_SENDER "kylincaster@foxmail.com" -#define DEFAULT_EMAIL_TIME 30 \ No newline at end of file +#define DEFAULT_EMAIL_TIME 45.0 \ No newline at end of file diff --git a/main.c b/main.c index d12c249..0a0fdff 100644 --- a/main.c +++ b/main.c @@ -576,7 +576,7 @@ static void print_help(const char *cmd) { printf("Env vars:\n"); printf(" TS_SOCKET the path to the unix socket used by the ts command.\n"); printf(" TS_MAIL_FROM who send the result mail, default (%s)\n", DEFAULT_EMAIL_SENDER); - printf(" TS_MAIL_TIME the duration criterion to send a email, default (%.3d sec)\n", DEFAULT_EMAIL_TIME / 1000); + printf(" TS_MAIL_TIME the duration criterion to send a email, default (%.3f sec)\n", DEFAULT_EMAIL_TIME); printf(" TS_MAXFINISHED maximum finished jobs in the queue.\n"); printf(" TS_MAXCONN maximum number of ts connections at once.\n"); printf(" TS_ONFINISH binary called on job end (passes jobid, error, " From 16902ea4ec7b150e202de573e9ff16f32622a38e Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Thu, 12 Oct 2023 15:29:11 +0800 Subject: [PATCH 74/91] add the failure check --- jobs.c | 14 +++++++++----- sqlite.c | 2 ++ taskset.c | 2 ++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/jobs.c b/jobs.c index 3ef3dbe..9cc4971 100644 --- a/jobs.c +++ b/jobs.c @@ -83,11 +83,11 @@ static void send_mail_via_ssmtp(struct Job* p) { } // skip the short task if (real_ms < sstmp_skip_ms || p->email == NULL) return; - + const char* state = (p->result.errorlevel || p->result.signal || p->result.died_by_signal) ? "failed" : "finished"; const char* unit = time_rep(&real_ms); char cmd[1024]; - snprintf(cmd, 1023, "echo \"Subject: %s[%d] n_core: %d, Elsp %.3f %s from MSI\nFrom: TS<%s>\nTo: %s\n\n\n Cmd: %s exit-code:%d\n Output: %s\" | ssmtp %s", - p->label, p->jobid, p->num_slots, real_ms, unit, p->email, email_sender, p->command + p->command_strip, p->result.signal, p->output_filename, p->email); + snprintf(cmd, 1023, "echo \"Subject: %s[%d] n_core: %d, Elsp %.3f %s from MSI\nFrom: TS<%s>\nTo: %s\n\n\n Cmd: %s [%s] Output: %s\" | ssmtp %s", + p->label, p->jobid, p->num_slots, real_ms, unit, p->email, email_sender, p->command + p->command_strip, state, p->output_filename, p->email); fork_cmd(root_UID, NULL, cmd); } @@ -151,7 +151,7 @@ static void allocate_cores_ex(struct Job* p, const char* extra) { #ifdef TASKSET set_task_cores(p, extra); #else - kill_pid(pid, extra, NULL); + kill_pid(p->pid, extra, NULL); #endif } } @@ -1720,7 +1720,7 @@ void s_job_info(int s, int jobid) { if (p->cores != NULL) { fd_nprintf(s, 100, "Slots: %-3d \tTaskset: %s\n", p->num_slots, p->cores); } - #elif + #else fd_nprintf(s, 100, "Slots: %-3d\n", p->num_slots); #endif fd_nprintf(s, 100, "Output: %s\n", p->output_filename); @@ -1737,6 +1737,10 @@ void s_job_info(int s, int jobid) { } const char *unit = time_rep(&t); fd_nprintf(s, 100, "Time running: %.4f %s\n", t, unit); + if (p->state == FINISHED) { + struct Result *res = &(p->result); + fd_nprintf(s, 100, "Error: %d Signal: %d Die: %d\n", res->errorlevel, res->signal, res->died_by_signal); + } // fd_nprintf(s, 100, "\n"); } diff --git a/sqlite.c b/sqlite.c index f16a596..c175850 100644 --- a/sqlite.c +++ b/sqlite.c @@ -365,7 +365,9 @@ int read_jobid_DB(int** jobids, const char* table) { struct Job* read_DB(int jobid, const char* table) { struct Job* job = (struct Job*)malloc(sizeof(struct Job)*1); + #ifdef TASKSET job->cores = NULL; + #endif struct Result* result = &(job->result); struct Procinfo* info= &(job->info); diff --git a/taskset.c b/taskset.c index d3adeb4..511a06f 100644 --- a/taskset.c +++ b/taskset.c @@ -54,6 +54,7 @@ void lock_core_by_job(struct Job* p) { } void unlock_core_by_job(struct Job* p) { + #ifdef TASKSET if (p == NULL) return; for (int i = 0; i < MAX_CORE_NUM; i++) { if (core_jobs[i] == p) { @@ -63,6 +64,7 @@ void unlock_core_by_job(struct Job* p) { } free(p->cores); p->cores = NULL; + #endif } int set_task_cores(struct Job* p, const char* extra) { From 96460d3b1d4f47b08f5d906eefba537f0415fdca Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Fri, 27 Oct 2023 14:19:09 +0800 Subject: [PATCH 75/91] fixed the bug on the job control and change the command --- README.md | 52 ++-- client.c | 16 +- jobs.c | 677 ++++++++++++++++++++++++++----------------------- main.c | 233 +++++++++-------- main.h | 36 +-- server.c | 24 +- server_start.c | 1 + user.c | 5 +- 8 files changed, 553 insertions(+), 491 deletions(-) diff --git a/README.md b/README.md index 8562da3..2e96707 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,11 @@ if you don't need the processors binding feature, try to remove `-DTASKSET` opti #define DEFAULT_USER_PATH "/home/kylin/task-spooler/user.txt" #define DEFAULT_LOG_PATH "/home/kylin/task-spooler/log.txt" #define DEFAULT_SQLITE_PATH "/home/kylin/task-spooler/task-spooler.db" +#define DEFAULT_NOTIFICATION_SOUND "/home/kylin/task-spooler/notifications-sound.wav" +#define DEFAULT_ERROR_SOUND "/home/kylin/task-spooler/error.wav" +#define DEFAULT_PULSE_SERVER "unix:/mnt/wslg/PulseServer" +#define DEFAULT_EMAIL_SENDER "kylincaster@foxmail.com" +#define DEFAULT_EMAIL_TIME 45.0% ``` You can specific the positions by the environment variables `TS_USER_PATH`, `TS_LOGFILE_PATH`, and `TS_SQLITE_PATH`, respectively on the invoking of daemon server. Otherwise, you could specify the positions in the `user config` file. @@ -144,12 +149,13 @@ Kylin wrote the multiple user support, fatal crush recovery through Sqlite3 data See below or `man ts` for more details. ``` -Task Spooler 2.0.0 - a task queue system for the unix user. +.1.0 - a task queue system for the unix user. Copyright (C) 2007-2023 Kylin JIANG - Duc Nguyen - Lluis Batlle i Rossell usage: ts [action] [-ngfmdE] [-L ] [-D ] [cmd...] Env vars: TS_SOCKET the path to the unix socket used by the ts command. - TS_MAILTO where to mail the result (on -m). Local user by default. + TS_MAIL_FROM who send the result mail, default (kylincaster@foxmail.com) + TS_MAIL_TIME the duration criterion to send a email, default (45.000 sec) TS_MAXFINISHED maximum finished jobs in the queue. TS_MAXCONN maximum number of ts connections at once. TS_ONFINISH binary called on job end (passes jobid, error, outfile, command). @@ -170,28 +176,28 @@ Long option actions: --full_cmd || -F [id] show full command. Of the last added, if not specified. --check_daemon Check the daemon is running or not. --count_running || -R return the number of running jobs --last_queue_id || -q show the job ID of the last added. - --get_logdir get the path containing log files. - --set_logdir [path] set the path containing log files. - --serialize || -M [format] serialize the job list to the specified format. - Choices: {default, json, tab}. - --daemon Run the server as daemon by Root only. - --pause [jobid] hold on a task. - --rerun [jobid] rerun a paused task. - --lock Locker the server (Timeout: 30 sec.) For Root, timeout is infinity. - --unlock Unlocker the server. - --stop [user] For normal user, pause all tasks and lock the account. - For root, to lock all users or single [user]. - --cont [user] For normal user, continue all paused tasks and lock the account. - For root, to unlock all users or single [user]. - --relink [PID] Relink the running tasks by its [PID] from an expected failure. + --get_logdir Retrieve the path where log files are stored. + --set_logdir [path] Set the path for storing log files. + --serialize || -M [format] Serialize the job list to the specified format. Options: {default, json, tab}. + --daemon Run the server as a daemon (Root access only). + --tmp save the logfile to tmp folder + --hold [jobid] Pause a specific task by its job ID. + --cont [jobid] Resume a paused task by its job ID. + --suspend [user] For regular users, pause all tasks and lock the user account. + For root user, lock all user accounts or a specific user's account. + --resume [user] For regular users, resume all paused tasks and unlock the user account. + For root user, unlock all user accounts or a specific user's account. + --lock Lock the server (Timeout: 30 seconds). For root user, there is no timeout. + --unlock Unlock the server. + --relink [PID] Relink running tasks using their [PID] in case of an unexpected failure. --job [joibid] || -J [joibid] set the jobid of the new or relink job Actions: - -A Show all users information - -X Refresh the user config by UID (Max. 100 users and only available for root) - -K kill the task spooler server (only available for root) - -C clear the list of finished jobs for current user - -l show the job list (default action) - -S [num] get/set the number of max simultaneous jobs of the server. (only available for root) + -A Display information for all users. + -X Update user configuration by UID (Max. 100 users, root access only) + -K Terminate the task spooler server (root access only) + -C Clear the list of finished jobs for the current user. + -l Show the job list (default action). + -S [num] Get/Set the maximum number of simultaneous server jobs (root access only). -t [id] "tail -n 10 -f" the output of the job. Last run if not specified. -c [id] like -t, but shows all the lines. Last run if not specified. -p [id] show the PID of the job. Last run if not specified. @@ -213,7 +219,7 @@ Options adding jobs: -O Set name of the log file (without any path). -z gzip the stored output (if not -n). -f don't fork into background. - -m send the output by e-mail (uses sendmail). + -m send the output by e-mail (uses ssmtp). -d the job will be run after the last job ends. -D the job will be run after the job of given IDs ends. -W the job will be run after the job of given IDs ends well (exit code 0). diff --git a/client.c b/client.c index 57d551c..dd6d69f 100644 --- a/client.c +++ b/client.c @@ -346,18 +346,18 @@ int c_unlock_server() { return error_sig; } -void c_stop_user(int uid) { +void c_suspend_user(int uid) { struct Msg m = default_msg(); // int res; - m.type = STOP_USER; + m.type = SUSPEND_USER; m.jobid = uid; send_msg(server_socket, &m); } -void c_cont_user(int uid) { +void c_resume_user(int uid) { struct Msg m = default_msg(); // int res; - m.type = CONT_USER; + m.type = RESUME_USER; m.jobid = uid; send_msg(server_socket, &m); } @@ -496,7 +496,7 @@ static char *get_output_file(int *pid) { return 0; } -void c_pause_job(int jobid) { +void c_hold_job(int jobid) { /* This will exit if there is any error */ /* int pid = 0; @@ -512,13 +512,13 @@ void c_pause_job(int jobid) { // kill(-pid, SIGSTOP); struct Msg m = default_msg(); - m.type = PAUSE_JOB; + m.type = HOLD_JOB; m.jobid = jobid; send_msg(server_socket, &m); c_wait_server_lines(); } -void c_rerun_job(int jobid) { +void c_cont_job(int jobid) { /* int pid = 0; get_output_file(&pid); @@ -533,7 +533,7 @@ void c_rerun_job(int jobid) { // kill(-pid, SIGCONT); struct Msg m = default_msg(); - m.type = RERUN_JOB; + m.type = CONT_JOB; m.jobid = jobid; send_msg(server_socket, &m); // not error, restart job diff --git a/jobs.c b/jobs.c index 9cc4971..3ead5be 100644 --- a/jobs.c +++ b/jobs.c @@ -11,22 +11,23 @@ #include #include #include -#include #include +#include #include #include -#include "main.h" -#include "user.h" #include "cjson/cJSON.h" #include "default.inc" +#include "main.h" +#include "user.h" /* The list will access them */ int busy_slots = 0; int max_slots = 1; -float sstmp_skip_ms = DEFAULT_EMAIL_TIME; // 200000; // skip task smaller than 200 s +float sstmp_skip_ms = + DEFAULT_EMAIL_TIME; // 200000; // skip task smaller than 200 s -char* email_sender; +char *email_sender; struct Notify { int socket; @@ -51,12 +52,12 @@ static char buff[256]; int max_jobs; static struct Job *get_job(int jobid); -static int fork_cmd(int UID, const char* path, const char* cmd); +static int fork_cmd(int UID, const char *path, const char *cmd); void notify_errorlevel(struct Job *p); -void s_set_jobids(int i) { - jobids = i; +void s_set_jobids(int i) { + jobids = i; set_jobids_DB(i); } @@ -65,7 +66,7 @@ void setup_ssmtp() { if (email_sender == NULL) { email_sender = DEFAULT_EMAIL_SENDER; } - char* time_s = getenv("TS_MAIL_TIME"); + char *time_s = getenv("TS_MAIL_TIME"); if (time_s != NULL) { float time_sec; int ret = sscanf(time_s, "%f", &time_sec); @@ -75,40 +76,51 @@ void setup_ssmtp() { } } -static void send_mail_via_ssmtp(struct Job* p) { +static void send_mail_via_ssmtp(struct Job *p) { float real_ms = p->result.real_ms; // units in second if (real_ms == 0.0) { real_ms = p->info.end_time.tv_sec - p->info.start_time.tv_sec; real_ms += 1e-6 * (p->info.end_time.tv_usec - p->info.start_time.tv_usec); } // skip the short task - if (real_ms < sstmp_skip_ms || p->email == NULL) return; - const char* state = (p->result.errorlevel || p->result.signal || p->result.died_by_signal) ? "failed" : "finished"; - const char* unit = time_rep(&real_ms); + if (real_ms < sstmp_skip_ms || p->email == NULL) + return; + const char *state = + (p->result.errorlevel || p->result.signal || p->result.died_by_signal) + ? "failed" + : "finished"; + const char *unit = time_rep(&real_ms); char cmd[1024]; - snprintf(cmd, 1023, "echo \"Subject: %s[%d] n_core: %d, Elsp %.3f %s from MSI\nFrom: TS<%s>\nTo: %s\n\n\n Cmd: %s [%s] Output: %s\" | ssmtp %s", - p->label, p->jobid, p->num_slots, real_ms, unit, p->email, email_sender, p->command + p->command_strip, state, p->output_filename, p->email); + snprintf(cmd, 1023, + "echo \"Subject: %s[%d] n_core: %d, Elsp %.3f %s from MSI\nFrom: " + "TS<%s>\nTo: %s\n\n\n Cmd: %s [%s] Output: %s\" | ssmtp %s", + p->label, p->jobid, p->num_slots, real_ms, unit, p->email, + email_sender, p->command + p->command_strip, state, + p->output_filename, p->email); fork_cmd(root_UID, NULL, cmd); } -static void sound_notify(struct Job* p) { - #ifdef SOUND +static void sound_notify(struct Job *p) { +#ifdef SOUND float real_ms = p->result.real_ms; if (real_ms == 0.0) { real_ms = p->info.end_time.tv_sec - p->info.start_time.tv_sec; real_ms += 1e-6 * (p->info.end_time.tv_usec - p->info.start_time.tv_usec); } // skip the short task - if (real_ms < 5) return; + if (real_ms < 5) + return; char cmd[256]; if (p->result.errorlevel == 0) { - snprintf(cmd, 255, "paplay -p \"%s\" -s %s", DEFAULT_NOTIFICATION_SOUND, DEFAULT_PULSE_SERVER); + snprintf(cmd, 255, "paplay -p \"%s\" -s %s", DEFAULT_NOTIFICATION_SOUND, + DEFAULT_PULSE_SERVER); } else { - snprintf(cmd, 255, "paplay -p \"%s\" -s %s", DEFAULT_ERROR_SOUND, DEFAULT_PULSE_SERVER); + snprintf(cmd, 255, "paplay -p \"%s\" -s %s", DEFAULT_ERROR_SOUND, + DEFAULT_PULSE_SERVER); } printf("%s\n", cmd); fork_cmd(user_UID[p->ts_UID], NULL, cmd); - #endif +#endif } static void destroy_job(struct Job *p) { @@ -120,15 +132,16 @@ static void destroy_job(struct Job *p) { pinfo_free(&p->info); free(p->depend_on); free(p->label); - #ifdef TASKSET +#ifdef TASKSET free(p->cores); - #endif +#endif free(p); } } -static void free_cores(struct Job* p) { - if (p == NULL && p->num_allocated == 0) return; +static void free_cores(struct Job *p) { + if (p == NULL && p->num_allocated == 0) + return; int ts_UID = p->ts_UID; user_busy[ts_UID] -= p->num_slots; busy_slots -= p->num_slots; @@ -140,30 +153,37 @@ static void free_cores(struct Job* p) { #endif } -static void allocate_cores_ex(struct Job* p, const char* extra) { - if (p == NULL) return; - int ts_UID = p->ts_UID; - user_busy[ts_UID] += p->num_slots; - busy_slots += p->num_slots; - p->num_allocated = p->num_slots; - user_jobs[ts_UID]++; - if (p -> state == RUNNING) { - #ifdef TASKSET - set_task_cores(p, extra); - #else - kill_pid(p->pid, extra, NULL); - #endif +static int allocate_cores_ex(struct Job *p, const char *extra) { + if (p == NULL) + return 0; + + if (p->state == RUNNING || p->state == LOCKED) { +#ifdef TASKSET + set_task_cores(p, extra); +#else + kill_pid(p->pid, extra, NULL); +#endif } + + if (extra == 0 || is_sleep(p->pid) == 0) { + int ts_UID = p->ts_UID; + user_busy[ts_UID] += p->num_slots; + busy_slots += p->num_slots; + p->num_allocated = p->num_slots; + user_jobs[ts_UID]++; + return 1; + } + return 0; } -static void allocate_cores(struct Job* p) { allocate_cores_ex(p, NULL); } +static void allocate_cores(struct Job *p) { allocate_cores_ex(p, NULL); } int num_pause; -int* paused_pids = NULL; +int *paused_pids = NULL; void init_pause() { num_pause = 0; - paused_pids = (int*) malloc(sizeof(int) * max_jobs); + paused_pids = (int *)malloc(sizeof(int) * max_jobs); } static int set_pause(int pid) { @@ -172,8 +192,7 @@ static int set_pause(int pid) { } static int free_pause(int pid) { - for (int i = 0; i < num_pause; i++) - { + for (int i = 0; i < num_pause; i++) { if (paused_pids[i] == pid) { num_pause--; paused_pids[i] = paused_pids[num_pause]; @@ -190,7 +209,7 @@ void check_pause() { int res = is_sleep(pid); if (res == 0) { kill_pid(pid, "kill -s STOP", NULL); - } else if(res == -1) { + } else if (res == -1) { num_pause--; paused_pids[i] = paused_pids[num_pause]; continue; @@ -205,125 +224,122 @@ void free_pause_array() { paused_pids = NULL; } -/* Serialize a job and add it to the JSON array. Returns 1 for success, 0 for failure. */ +/* Serialize a job and add it to the JSON array. Returns 1 for success, 0 for + * failure. */ static int add_job_to_json_array(struct Job *p, cJSON *jobs) { - cJSON *job = cJSON_CreateObject(); - if (job == NULL) - { - error("Error initializing JSON object for job %i.", p->jobid); - return 0; - } - cJSON_AddItemToArray(jobs, job); + cJSON *job = cJSON_CreateObject(); + if (job == NULL) { + error("Error initializing JSON object for job %i.", p->jobid); + return 0; + } + cJSON_AddItemToArray(jobs, job); - /* Add fields */ - cJSON *field; + /* Add fields */ + cJSON *field; - /* ID */ - field = cJSON_CreateNumber(p->jobid); - if (field == NULL) - { - error("Error initializing JSON object for job %i field ID.", p->jobid); - return 0; - } - cJSON_AddItemToObject(job, "ID", field); - - /* State */ - const char *state_string = jstate2string(p->state); - field = cJSON_CreateStringReference(state_string); - if (field == NULL) - { - error("Error initializing JSON object for job %i field State (value %d/%s).", p->jobid, p->state, state_string); - return 0; - } - cJSON_AddItemToObject(job, "State", field); - - /* num_slots */ - field = cJSON_CreateNumber(p->num_slots); - if (field == NULL) - { - error("Error initializing JSON object for job %i field ID.", p->jobid); - return 0; - } - cJSON_AddItemToObject(job, "Proc.", field); - - /* user */ - field = cJSON_CreateStringReference(user_name[p->ts_UID]); - if (field == NULL) - { - error("Error initializing JSON object for job %i field State (value %d/%s).", p->jobid, p->state, state_string); - return 0; - } - cJSON_AddItemToObject(job, "User", field); + /* ID */ + field = cJSON_CreateNumber(p->jobid); + if (field == NULL) { + error("Error initializing JSON object for job %i field ID.", p->jobid); + return 0; + } + cJSON_AddItemToObject(job, "ID", field); - /* label */ + /* State */ + const char *state_string = jstate2string(p->state); + field = cJSON_CreateStringReference(state_string); + if (field == NULL) { + error( + "Error initializing JSON object for job %i field State (value %d/%s).", + p->jobid, p->state, state_string); + return 0; + } + cJSON_AddItemToObject(job, "State", field); - if (p->label != NULL) { - field = cJSON_CreateStringReference(p->label); - } - else { - field = cJSON_CreateNull(); - } - if (field == NULL) - { - error("Error initializing JSON object for job %i field State (value %d/%s).", p->jobid, p->state, state_string); - return 0; - } - cJSON_AddItemToObject(job, "Label", field); - - /* Output */ - field = cJSON_CreateStringReference(p->output_filename); - if (field == NULL) - { - error("Error initializing JSON object for job %i field Output (value %s).", p->jobid, p->output_filename); - return 0; - } - cJSON_AddItemToObject(job, "Output", field); + /* num_slots */ + field = cJSON_CreateNumber(p->num_slots); + if (field == NULL) { + error("Error initializing JSON object for job %i field ID.", p->jobid); + return 0; + } + cJSON_AddItemToObject(job, "Proc.", field); - /* E-Level */ - if (p->state == FINISHED) { - field = cJSON_CreateNumber(p->result.errorlevel); - } - else { - field = cJSON_CreateNull(); - } - if (field == NULL) - { - error("Error initializing JSON object for job %i field E-Level.", p->jobid); - return 0; - } - cJSON_AddItemToObject(job, "E-Level", field); + /* user */ + field = cJSON_CreateStringReference(user_name[p->ts_UID]); + if (field == NULL) { + error( + "Error initializing JSON object for job %i field State (value %d/%s).", + p->jobid, p->state, state_string); + return 0; + } + cJSON_AddItemToObject(job, "User", field); - /* Time */ - if (p->state == FINISHED) { - field = cJSON_CreateNumber(p->result.real_ms); - if (field == NULL) - { - error("Error initializing JSON object for job %i field Time_ms (value %d).", p->result.real_ms); - return 0; - } - } - else { - field = cJSON_CreateNull(); - if (field == NULL) - { - error("Error initializing JSON object for job %i field Time_ms (no result)."); - return 0; - } + /* label */ + + if (p->label != NULL) { + field = cJSON_CreateStringReference(p->label); + } else { + field = cJSON_CreateNull(); + } + if (field == NULL) { + error( + "Error initializing JSON object for job %i field State (value %d/%s).", + p->jobid, p->state, state_string); + return 0; + } + cJSON_AddItemToObject(job, "Label", field); + + /* Output */ + field = cJSON_CreateStringReference(p->output_filename); + if (field == NULL) { + error("Error initializing JSON object for job %i field Output (value %s).", + p->jobid, p->output_filename); + return 0; + } + cJSON_AddItemToObject(job, "Output", field); + + /* E-Level */ + if (p->state == FINISHED) { + field = cJSON_CreateNumber(p->result.errorlevel); + } else { + field = cJSON_CreateNull(); + } + if (field == NULL) { + error("Error initializing JSON object for job %i field E-Level.", p->jobid); + return 0; + } + cJSON_AddItemToObject(job, "E-Level", field); + + /* Time */ + if (p->state == FINISHED) { + field = cJSON_CreateNumber(p->result.real_ms); + if (field == NULL) { + error( + "Error initializing JSON object for job %i field Time_ms (value %d).", + p->result.real_ms); + return 0; } - cJSON_AddItemToObject(job, "Time_ms", field); - - /* Command */ - field = cJSON_CreateStringReference(p->command + p->command_strip); - if (field == NULL) - { - error("Error initializing JSON object for job %i field Command (value %s).", p->jobid, p->command); - return 0; + } else { + field = cJSON_CreateNull(); + if (field == NULL) { + error("Error initializing JSON object for job %i field Time_ms (no " + "result)."); + return 0; } - cJSON_AddItemToObject(job, "Command", field); + } + cJSON_AddItemToObject(job, "Time_ms", field); - return 1; -} + /* Command */ + field = cJSON_CreateStringReference(p->command + p->command_strip); + if (field == NULL) { + error("Error initializing JSON object for job %i field Command (value %s).", + p->jobid, p->command); + return 0; + } + cJSON_AddItemToObject(job, "Command", field); + return 1; +} void send_list_line(int s, const char *str) { struct Msg m = default_msg(); @@ -392,7 +408,6 @@ static struct Job *find_previous_job(const struct Job *final) { return NULL; } - struct Job *findjob(int jobid) { struct Job *p; /* Show Queued or Running jobs */ @@ -405,14 +420,15 @@ struct Job *findjob(int jobid) { return NULL; } - -static struct Job* job_by_pid(int pid) { - if (pid == 0) return NULL; +static struct Job *job_by_pid(int pid) { + if (pid == 0) + return NULL; struct Job *p = &firstjob; while (p->next != NULL) { p = p->next; - if (p->pid == pid) return p; + if (p->pid == pid) + return p; } return NULL; } @@ -429,10 +445,10 @@ int s_check_running_pid(int pid) { // if any error return non-0; int s_check_relink(int s, int pid, int ts_UID) { - struct Job* p = job_by_pid(pid); + struct Job *p = job_by_pid(pid); if (p != NULL && (p->state != DELINK && p->state != WAIT)) { - sprintf(buff, " Error: PID [%i] is already in job as Jobid: %i [%s]\n", - pid, p->jobid, jstate2string(p->state)); + sprintf(buff, " Error: PID [%i] is already in job as Jobid: %i [%s]\n", + pid, p->jobid, jstate2string(p->state)); send_list_line(s, buff); return -1; } @@ -448,21 +464,22 @@ int s_check_relink(int s, int pid, int ts_UID) { } int job_tsUID = get_tsUID(t_stat.st_uid); - if (ts_UID == 0) { + if (ts_UID == 0) { ; } else if (ts_UID == job_tsUID) { ; } else { - snprintf(buff, 255, " Error: PID [%i] is owned by [%d] `%150s` not the user [%d] `%s`\n", - pid, user_UID[job_tsUID], user_name[job_tsUID], - user_UID[ts_UID], user_name[ts_UID]); + snprintf( + buff, 255, + " Error: PID [%i] is owned by [%d] `%150s` not the user [%d] `%s`\n", + pid, user_UID[job_tsUID], user_name[job_tsUID], user_UID[ts_UID], + user_name[ts_UID]); send_list_line(s, buff); return -1; } return job_tsUID; } - static struct Job *findjob_holding_client() { struct Job *p; @@ -529,7 +546,7 @@ void s_kill_all_jobs(int s, int ts_UID) { while (p != 0) { if (p->state == RUNNING && (ts_UID == 0 || p->ts_UID == ts_UID)) send(s, &p->pid, sizeof(int), 0); - + p = p->next; } } @@ -588,7 +605,8 @@ void s_get_label(int s, int jobid) { } if (p == 0) { - snprintf(buff, 255, "[get_label0] Job %i not finished or not running.\n", jobid); + snprintf(buff, 255, "[get_label0] Job %i not finished or not running.\n", + jobid); send_list_line(s, buff); return; } @@ -628,7 +646,8 @@ void s_send_cmd(int s, int jobid) { } if (p == 0) { - snprintf(buff, 255, "[get_label1] Job %i not finished or not running.\n", jobid); + snprintf(buff, 255, "[get_label1] Job %i not finished or not running.\n", + jobid); send_list_line(s, buff); return; } @@ -638,17 +657,17 @@ void s_send_cmd(int s, int jobid) { free(cmd); } -static char* get_ofile_from_FD(int pid) { +static char *get_ofile_from_FD(int pid) { char path[256], buff[256] = ""; snprintf(path, 255, "/proc/%d/fd/1", command_line.taskpid); int len = readlink(path, buff, sizeof(buff)); - // printf("path = %s, buff = %s\n", path, buff); + // printf("path = %s, buff = %s\n", path, buff); if (strlen(buff) == 0 || len == -1) { return NULL; } - int namesize = strnlen(buff, 255)+1; - char* f = (char*) malloc(namesize); + int namesize = strnlen(buff, 255) + 1; + char *f = (char *)malloc(namesize); strncpy(f, buff, namesize); return f; } @@ -767,8 +786,7 @@ void s_list(int s, int ts_UID, enum ListFormat listFormat) { } } else if (listFormat == JSON) { cJSON *jobs = cJSON_CreateArray(); - if (jobs == NULL) - { + if (jobs == NULL) { error("Error initializing JSON array."); goto end; } @@ -795,46 +813,45 @@ void s_list(int s, int ts_UID, enum ListFormat listFormat) { } buffer = cJSON_PrintUnformatted(jobs); - if (buffer == NULL) - { + if (buffer == NULL) { error("Error converting jobs to JSON."); goto end; } - + // append newline size_t buffer_strlen = strlen(buffer); - buffer = realloc(buffer, buffer_strlen+1+1); + buffer = realloc(buffer, buffer_strlen + 1 + 1); strcat(buffer, "\n"); send_list_line(s, buffer); goto end; - end: - cJSON_Delete(jobs); - free(buffer); + end: + cJSON_Delete(jobs); + free(buffer); // end of Json - } else if (listFormat == TAB) { - /* Show Queued or Running jobs */ - p = firstjob.next; - while (p != 0) { - if (p->state != HOLDING_CLIENT) { - buffer = joblist_line_plain(p); - send_list_line(s, buffer); - free(buffer); - } - p = p->next; - } + } else if (listFormat == TAB) { + /* Show Queued or Running jobs */ + p = firstjob.next; + while (p != 0) { + if (p->state != HOLDING_CLIENT) { + buffer = joblist_line_plain(p); + send_list_line(s, buffer); + free(buffer); + } + p = p->next; + } - p = first_finished_job.next; + p = first_finished_job.next; - /* Show Finished jobs */ - while (p != 0) { - buffer = joblist_line_plain(p); - send_list_line(s, buffer); - free(buffer); - p = p->next; - } - } // end of TAB + /* Show Finished jobs */ + while (p != 0) { + buffer = joblist_line_plain(p); + send_list_line(s, buffer); + free(buffer); + p = p->next; + } + } // end of TAB } void s_list_all(int s, enum ListFormat listFormat) { @@ -906,7 +923,7 @@ static struct Job *newjobptr() { p = p->next; p->next = (struct Job *)calloc(sizeof(struct Job), sizeof(char)); - + /* p->next->next = 0; p->next->output_filename = 0; @@ -969,7 +986,7 @@ static int find_last_stored_jobid_finished() { /* Returns job id or -1 on error */ int s_newjob(int s, struct Msg *m, int ts_UID) { - + struct Job *p = NULL; int res; // int waitjob_flag = 0; // 0 for newjob, 1 for WAIT and 2 for DELINK @@ -1004,7 +1021,7 @@ int s_newjob(int s, struct Msg *m, int ts_UID) { p->state = QUEUED; } else p->state = HOLDING_CLIENT; - + // manually relink if (m->u.newjob.taskpid != 0) { p->state = RELINK; @@ -1121,7 +1138,7 @@ int s_newjob(int s, struct Msg *m, int ts_UID) { /* load the command */ - char* buff = malloc(m->u.newjob.command_size); + char *buff = malloc(m->u.newjob.command_size); if (buff == 0) error("Cannot allocate memory in s_newjob command_size (%i)", m->u.newjob.command_size); @@ -1131,7 +1148,7 @@ int s_newjob(int s, struct Msg *m, int ts_UID) { p->command = buff; p->command_strip = m->u.newjob.command_size_strip; - + /* load the work dir */ p->work_dir = 0; if (m->u.newjob.path_size > 0) { @@ -1190,7 +1207,7 @@ int s_newjob(int s, struct Msg *m, int ts_UID) { if (p->state == DELINK) { p->state = RELINK; - // manually insert + // manually insert } else if (p->state == WAIT) { p->state = QUEUED; user_queue[p->ts_UID]++; @@ -1209,7 +1226,7 @@ int s_newjob(int s, struct Msg *m, int ts_UID) { insert_DB(p, "Jobs"); user_queue[p->ts_UID]++; } - + set_jobids_DB(jobids); return p->jobid; } @@ -1246,7 +1263,7 @@ void s_delete_job(int jobid) { } /* -1 if no one should be run. */ -/* +/* next_run_job() in `server.c` s_mark_job_running(newjob); @@ -1305,7 +1322,7 @@ int next_run_job() { int num_slots = p->num_slots, id = p->ts_UID; if (id == uid && free_slots >= num_slots && - user_max_slots[id] - user_busy[id] >= num_slots) { + user_max_slots[id] - user_busy[id] >= num_slots) { user_queue[id]--; return p->jobid; } @@ -1355,9 +1372,9 @@ static void new_finished_job(struct Job *j) { insert_DB(j, "Finished"); delete_DB(j->jobid, "Jobs"); - #ifdef TASKSET - unlock_core_by_job(j); - #endif +#ifdef TASKSET + unlock_core_by_job(j); +#endif sound_notify(j); send_mail_via_ssmtp(j); } @@ -1395,12 +1412,12 @@ static int in_notify_list(int jobid) { /* job_finished from running to jobid */ void job_finished(const struct Result *result, int jobid) { // printf("job_finished %d\n", jobid); - + if (busy_slots < 0) error( "Wrong state in the server. busy_slots = %i instead of greater than 0", busy_slots); - + struct Job *p = findjob(jobid); if (p == NULL) @@ -1457,46 +1474,45 @@ void job_finished(const struct Result *result, int jobid) { jpointer->next = newfirst; } - } -static int fork_cmd(int UID, const char* path, const char* cmd) { - int pid = -1; //定义一个进程ID变量 +static int fork_cmd(int UID, const char *path, const char *cmd) { + int pid = -1; //定义一个进程ID变量 - pid = fork(); //调用fork()函数创建子进程 - if (pid < 0) //如果返回值小于0,表示fork失败 - { - perror("fork error"); //打印错误信息 - return -1; - } - else if (pid == 0) //如果返回值等于0,表示子进程正在运行 - { - setuid(UID); - if (path != NULL) chdir(path); - system(cmd); - exit(0); - /* - int cmd_array_size; - printf("cmd = %s\n", cmd); - char** cmd_arry = split_str(cmd, &cmd_array_size); - if (cmd_array_size > 0) { - printf("run cmd %s\n", cmd_arry[0]); - system(cmd); - exit(0); - // execvp(cmd_arry[0], cmd_arry); - } - // execlp("ls", "-l", NULL); //执行ls -l命令,替换当前进程 - */ - return -1; - } - else //如果返回值大于0,表示父进程正在运行 - { - printf("[Child PID:%d] Add queued job: %s\n", pid, cmd); //打印子进程的ID - } - return pid; + pid = fork(); //调用fork()函数创建子进程 + if (pid < 0) //如果返回值小于0,表示fork失败 + { + perror("fork error"); //打印错误信息 + return -1; + } else if (pid == 0) //如果返回值等于0,表示子进程正在运行 + { + setuid(UID); + if (path != NULL) + chdir(path); + system(cmd); + exit(0); + /* + int cmd_array_size; + printf("cmd = %s\n", cmd); + char** cmd_arry = split_str(cmd, &cmd_array_size); + if (cmd_array_size > 0) { + printf("run cmd %s\n", cmd_arry[0]); + system(cmd); + exit(0); + // execvp(cmd_arry[0], cmd_arry); + } + // execlp("ls", "-l", NULL); //执行ls -l命令,替换当前进程 + */ + return -1; + } else //如果返回值大于0,表示父进程正在运行 + { + printf("[Child PID:%d] Add queued job: %s\n", pid, cmd); //打印子进程的ID + } + return pid; } -static void s_add_job(struct Job* j, struct Job** p) { - // if (j->state == RUNNING || j->state == HOLDING_CLIENT || j->state == RELINK) { +static void s_add_job(struct Job *j, struct Job **p) { + // if (j->state == RUNNING || j->state == HOLDING_CLIENT || j->state == + // RELINK) { if (j->state == RUNNING) { if (j->pid > 0 && s_check_running_pid(j->pid) == 1) { printf("add job %d\n", j->jobid); @@ -1509,8 +1525,8 @@ static void s_add_job(struct Job* j, struct Job** p) { (*p) = j; char c[64]; - sprintf(c, " --relink %d -J %d ", j->pid, j->jobid); - char* str = insert_chars(j->command_strip, j->command, c); + sprintf(c, " --relink %d -J %d ", j->pid, j->jobid); + char *str = insert_chars(j->command_strip, j->command, c); fork_cmd(user_UID[j->ts_UID], j->work_dir, str); // fork_cmd(0, j->work_dir, str); @@ -1525,17 +1541,15 @@ static void s_add_job(struct Job* j, struct Job** p) { if (j->state == QUEUED) { j->state = WAIT; } - + // jobDB_wait_num++; (*p)->next = j; (*p) = j; - - char c[32]; - sprintf(c, " -J %d ", j->jobid); - char* str = insert_chars(j->command_strip, j->command, c); - + sprintf(c, " -J %d ", j->jobid); + char *str = insert_chars(j->command_strip, j->command, c); + fork_cmd(user_UID[j->ts_UID], j->work_dir, str); jobids = jobids > j->jobid ? jobids : j->jobid + 1; j = NULL; @@ -1549,7 +1563,7 @@ static void s_add_job(struct Job* j, struct Job** p) { (*p) = j; */ } - + destroy_job(j); } @@ -1628,7 +1642,7 @@ void s_process_runjob_ok(int jobid, char *oname, int pid) { p->output_filename = oname; } pinfo_set_start_time_check(&p->info); - if (pid >0 && is_sleep(pid) == 0) { + if (pid > 0 && is_sleep(pid) == 0) { write_logfile(p); set_task_cores(p, NULL); } @@ -1693,7 +1707,8 @@ void s_job_info(int s, int jobid) { } if (p == 0) { - snprintf(buff, 255, "[s_send_runjob] Job %i not finished or not running.\n", jobid); + snprintf(buff, 255, "[s_send_runjob] Job %i not finished or not running.\n", + jobid); send_list_line(s, buff); return; } @@ -1710,19 +1725,23 @@ void s_job_info(int s, int jobid) { fd_nprintf(s, 100, ",%i", p->depend_on[i]); fd_nprintf(s, 100, "]&& "); } - write(s, p->command + p->command_strip, strlen(p->command + p->command_strip)); + write(s, p->command + p->command_strip, + strlen(p->command + p->command_strip)); fd_nprintf(s, 100, "\n"); - fd_nprintf(s, 100, "User: %s [%d]\n", user_name[p->ts_UID], user_UID[p->ts_UID]); - fd_nprintf(s, 100, "State: %-7s PID: %-6d\n", - jstate2string(p->state), p->pid); + fd_nprintf(s, 100, "User: %s [%d]\n", user_name[p->ts_UID], + user_UID[p->ts_UID]); + fd_nprintf(s, 100, "State: %-7s PID: %-6d\n", jstate2string(p->state), + p->pid); - #ifdef TASKSET +#ifdef TASKSET if (p->cores != NULL) { - fd_nprintf(s, 100, "Slots: %-3d \tTaskset: %s\n", p->num_slots, p->cores); + int buffer_len = strlen(p->cores) + 100; + fd_nprintf(s, buffer_len, "Slots: %-3d \tTaskset: %s\n", p->num_slots, + p->cores); } - #else - fd_nprintf(s, 100, "Slots: %-3d\n", p->num_slots); - #endif +#else + fd_nprintf(s, 100, "Slots: %-3d\n", p->num_slots); +#endif fd_nprintf(s, 100, "Output: %s\n", p->output_filename); fd_nprintf(s, 100, "Enqueue time: %s", ctime(&p->info.enqueue_time.tv_sec)); fd_nprintf(s, 100, "Start time: %s", ctime(&p->info.start_time.tv_sec)); @@ -1739,7 +1758,8 @@ void s_job_info(int s, int jobid) { fd_nprintf(s, 100, "Time running: %.4f %s\n", t, unit); if (p->state == FINISHED) { struct Result *res = &(p->result); - fd_nprintf(s, 100, "Error: %d Signal: %d Die: %d\n", res->errorlevel, res->signal, res->died_by_signal); + fd_nprintf(s, 100, "Error: %d Signal: %d Die: %d\n", res->errorlevel, + res->signal, res->died_by_signal); } // fd_nprintf(s, 100, "\n"); } @@ -1757,19 +1777,19 @@ void s_refresh_users(int s) { send_list_line(s, "refresh the list success!\n"); } -void s_stop_all_users(int s) { +void s_suspend_user_all(int s) { for (int i = 1; i < user_number; i++) { - s_stop_user(s, i); + s_suspend_user(s, i); } } -void s_cont_all_users(int s) { +void s_resume_user_all(int s) { for (int i = 1; i < user_number; i++) { - s_cont_user(s, i); + s_resume_user(s, i); } } -void s_cont_user(int s, int ts_UID) { +void s_resume_user(int s, int ts_UID) { // get the sequence of ts_UID if (ts_UID < 0 || ts_UID > USER_MAX) return; @@ -1782,10 +1802,14 @@ void s_cont_user(int s, int ts_UID) { if (p->ts_UID == ts_UID && p->state == LOCKED) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { + printf("pid = %d\n", p->pid); if (is_sleep(p->pid) == 1) { - p->state = RUNNING; - free_pause(p->pid); - allocate_cores_ex(p, "kill -s CONT"); + printf("allocate = %d\n", p->pid); + int status = allocate_cores_ex(p, "kill -s CONT"); + if (status == 1) { + p->state = RUNNING; + free_pause(p->pid); + } } // kill_pid(p->pid, "kill -s CONT"); } @@ -1793,11 +1817,12 @@ void s_cont_user(int s, int ts_UID) { p = p->next; } - snprintf(buff, 255, "Resume user: [%d] %199s\n", user_UID[ts_UID], user_name[ts_UID]); + snprintf(buff, 255, "Resume user: [%d] %199s\n", user_UID[ts_UID], + user_name[ts_UID]); send_list_line(s, buff); } -void s_stop_user(int s, int ts_UID) { +void s_suspend_user(int s, int ts_UID) { // get the sequence of ts_UID if (ts_UID < 0 || ts_UID > USER_MAX) return; @@ -1811,10 +1836,12 @@ void s_stop_user(int s, int ts_UID) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { if (is_sleep(p->pid) == 0) { - p->state = LOCKED; - free_cores(p); - set_pause(p->pid); kill_pid(p->pid, "kill -s STOP", NULL); + if (is_sleep(p->pid) == 1) { + p->state = LOCKED; + free_cores(p); + set_pause(p->pid); + } } } else { char *label = "(...)"; @@ -1828,7 +1855,8 @@ void s_stop_user(int s, int ts_UID) { p = p->next; } - snprintf(buff, 255, "Lock user: [%d] %s\n", user_UID[ts_UID], user_name[ts_UID]); + snprintf(buff, 255, "Lock user: [%d] %s\n", user_UID[ts_UID], + user_name[ts_UID]); send_list_line(s, buff); } @@ -1865,7 +1893,8 @@ void s_send_output(int s, int jobid) { if (jobid == -1) snprintf(buff, 255, "The last job has not finished or is not running.\n"); else - snprintf(buff, 255, "[s_send_output] Job %i not finished or not running.\n", jobid); + snprintf(buff, 255, + "[s_send_output] Job %i not finished or not running.\n", jobid); send_list_line(s, buff); return; } @@ -1913,7 +1942,7 @@ int s_remove_job(int s, int *jobid, int client_tsUID) { struct Msg m = default_msg(); struct Job *before_p = &firstjob; - if (client_tsUID < 0 || client_tsUID > USER_MAX) { + if (client_tsUID < 0 || client_tsUID > USER_MAX) { snprintf(buff, 255, "invalid ts_UID [%d] in job removal.\n", client_tsUID); send_list_line(s, buff); return 0; @@ -1968,15 +1997,16 @@ int s_remove_job(int s, int *jobid, int client_tsUID) { if (p == NULL) { snprintf(buff, 255, "The job %i is not in queue.\n", *jobid); } else { - snprintf(buff, 255, "The job %i is owned by [%d] `%s` not the user [%d] `%s`.\n", - *jobid, user_UID[p->ts_UID], user_name[p->ts_UID], - user_UID[client_tsUID], user_name[client_tsUID]); + snprintf(buff, 255, + "The job %i is owned by [%d] `%s` not the user [%d] `%s`.\n", + *jobid, user_UID[p->ts_UID], user_name[p->ts_UID], + user_UID[client_tsUID], user_name[client_tsUID]); } } if (p != NULL) { if (p->ts_UID != client_tsUID) { - snprintf(buff, 255, "The job %i belongs to %s not %s.\n", - *jobid, user_name[p->ts_UID], user_name[client_tsUID]); + snprintf(buff, 255, "The job %i belongs to %s not %s.\n", *jobid, + user_name[p->ts_UID], user_name[client_tsUID]); } } send_list_line(s, buff); @@ -1988,7 +2018,8 @@ int s_remove_job(int s, int *jobid, int client_tsUID) { if (*jobid == -1) snprintf(buff, 255, "Running job of last job is removed.\n"); else - snprintf(buff, 255, "Running job [%i] PID: %d by `%s` is removed.\n", *jobid, p->pid, user_name[p->ts_UID]); + snprintf(buff, 255, "Running job [%i] PID: %d by `%s` is removed.\n", + *jobid, p->pid, user_name[p->ts_UID]); send_list_line(s, buff); return 0; } @@ -2099,15 +2130,17 @@ void s_lock_server(int s, int ts_UID) { user_locker = ts_UID; locker_time = time(NULL); snprintf(buff, 255, "lock the task-spooler server by [%d] `%s`\n", - user_UID[user_locker], user_name[ts_UID]); + user_UID[user_locker], user_name[ts_UID]); } else { if (user_locker == ts_UID) { - snprintf(buff, 255, - "The task-spooler server has already been locked by [%d] `%s`\n", - user_UID[user_locker], user_name[user_locker]); + snprintf( + buff, 255, + "The task-spooler server has already been locked by [%d] `%s`\n", + user_UID[user_locker], user_name[user_locker]); } else { snprintf(buff, 255, - "Error: the task-spooler server has already been locked by other user [%d] `%s`\n", + "Error: the task-spooler server has already been locked by " + "other user [%d] `%s`\n", user_UID[user_locker], user_name[user_locker]); } } @@ -2117,12 +2150,11 @@ void s_lock_server(int s, int ts_UID) { void s_unlock_server(int s, int ts_UID) { if (user_locker == -1) { - snprintf(buff, 255, - "The task-spooler server has already been unlocked\n"); + snprintf(buff, 255, "The task-spooler server has already been unlocked\n"); } else { if (ts_UID == 0) { - user_locker = -1; - snprintf(buff, 255, "Unlock the task-spooler server by Root\n"); + user_locker = -1; + snprintf(buff, 255, "Unlock the task-spooler server by Root\n"); } else { if (user_locker == ts_UID) { user_locker = -1; @@ -2130,17 +2162,16 @@ void s_unlock_server(int s, int ts_UID) { user_UID[ts_UID], user_name[ts_UID]); } else { snprintf(buff, 255, - "Error: the task-spooler server locked by other user cannot be unlocked by [%d] `%s`\n", + "Error: the task-spooler server locked by other user cannot " + "be unlocked by [%d] `%s`\n", user_UID[ts_UID], user_name[ts_UID]); - } } - } send_list_line(s, buff); } -static void s_lock_queue(struct Job* p) { +static void s_lock_queue(struct Job *p) { if (p->state == QUEUED) { user_queue[p->ts_UID]--; p->state = LOCKED; @@ -2148,7 +2179,7 @@ static void s_lock_queue(struct Job* p) { } } -static void s_unlock_queue(struct Job* p) { +static void s_unlock_queue(struct Job *p) { if (p->state == LOCKED) { user_queue[p->ts_UID]++; p->state = QUEUED; @@ -2156,8 +2187,7 @@ static void s_unlock_queue(struct Job* p) { } } - -void s_pause_job(int s, int jobid, int ts_UID) { +void s_hold_job(int s, int jobid, int ts_UID) { if (user_max_slots[ts_UID] < 0) { snprintf(buff, 255, "Error: The owner `%s` is locked\n", user_name[ts_UID]); send_list_line(s, buff); @@ -2196,12 +2226,14 @@ void s_pause_job(int s, int jobid, int ts_UID) { send_list_line(s, buff); return; } - + int job_tsUID = p->ts_UID; if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { - set_pause(p->pid); - free_cores(p); kill_pid(p->pid, "kill -s STOP", NULL); + if (is_sleep(p->pid) == 1) { + set_pause(p->pid); + free_cores(p); + } snprintf(buff, 255, "To pause job [%d] successfully!\n", jobid); } else { snprintf(buff, 255, "Error: cannot pause job [%d]\n", jobid); @@ -2209,8 +2241,8 @@ void s_pause_job(int s, int jobid, int ts_UID) { send_list_line(s, buff); } -void s_rerun_job(int s, int jobid, int ts_UID) { - if (user_max_slots[ts_UID] < 0) { +void s_cont_job(int s, int jobid, int ts_UID) { + if (user_max_slots[ts_UID] < 0) { snprintf(buff, 255, "Error: The owner `%s` is locked\n", user_name[ts_UID]); send_list_line(s, buff); return; @@ -2223,8 +2255,8 @@ void s_rerun_job(int s, int jobid, int ts_UID) { send_list_line(s, buff); return; } - -if (p->state == LOCKED) { + + if (p->state == LOCKED) { if (p->ts_UID == ts_UID || ts_UID == 0) { snprintf(buff, 255, "The locked job [%d] is in queue.\n", jobid); s_unlock_queue(p); @@ -2253,9 +2285,14 @@ if (p->state == LOCKED) { int job_tsUID = p->ts_UID; if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { int num_slots = p->num_slots; - if (user_busy[ts_UID] + num_slots <= user_max_slots[ts_UID] && busy_slots + num_slots <= max_slots) { - free_pause(p->pid); - allocate_cores_ex(p, "kill -s CONT"); + if (user_busy[ts_UID] + num_slots <= user_max_slots[ts_UID] && + busy_slots + num_slots <= max_slots) { + + int status = allocate_cores_ex(p, "kill -s CONT"); + if (status == 1) { + p->state = RUNNING; + free_pause(p->pid); + } snprintf(buff, 255, "To rerun job [%d] successfully!\n", jobid); } else { snprintf(buff, 255, "Error: not enough slots [%d]\n", jobid); diff --git a/main.c b/main.c index 0a0fdff..f1e49c4 100644 --- a/main.c +++ b/main.c @@ -12,12 +12,14 @@ #include #include #include +#include // time() #include -#include // time() +#include "default.inc" #include "main.h" #include "version.h" -#include "default.inc" + +#include "user.h" int client_uid; extern char *optarg; @@ -34,9 +36,12 @@ static char *old_getopt_env; static char version[1024]; static void init_version() { - char *ts_version = TS_MAKE_STR(TS_VERSION); - sprintf(version, "Task Spooler %s - a task queue system for the unix user.\n" - "Copyright (C) 2007-%d Kylin JIANG - Duc Nguyen - Lluis Batlle i Rossell", ts_version, 2023); + char *ts_version = TS_MAKE_STR(TS_VERSION); + sprintf(version, + "Task Spooler %s - a task queue system for the unix user.\n" + "Copyright (C) 2007-%d Kylin JIANG - Duc Nguyen - Lluis Batlle i " + "Rossell", + ts_version, 2023); } static void default_command_line() { @@ -81,38 +86,39 @@ void get_command(int index, int argc, char **argv) { command_line.command.num = argc - index; } -char* get_tmp() { - const char* tmpFolder = getenv("TMPDIR"); - if (tmpFolder == NULL) { - tmpFolder = "/tmp/"; - } - const char* fileNameFormat = "ts_out.%06d"; - const int folderLength = strlen(tmpFolder); - const int maxFileNameLength = folderLength + strlen(fileNameFormat) + 10; +char *get_tmp() { + const char *tmpFolder = getenv("TMPDIR"); + if (tmpFolder == NULL) { + tmpFolder = "/tmp/"; + } + const char *fileNameFormat = "ts_out.%06d"; + const int folderLength = strlen(tmpFolder); + const int maxFileNameLength = folderLength + strlen(fileNameFormat) + 10; - char* fileName = (char*)malloc(maxFileNameLength * sizeof(char)); - char* fileName2 = (char*)malloc(maxFileNameLength * sizeof(char)); + char *fileName = (char *)malloc(maxFileNameLength * sizeof(char)); + char *fileName2 = (char *)malloc(maxFileNameLength * sizeof(char)); - if (fileName == NULL || fileName2 == NULL) { - fprintf(stderr, "Memory allocation failed.\n"); - return NULL; - } + if (fileName == NULL || fileName2 == NULL) { + fprintf(stderr, "Memory allocation failed.\n"); + return NULL; + } - srand(time(NULL)); - uint randomNumber1 = rand() % 10000; - uint randomNumber2 = rand() % 10000; - uint randomNumber3 = rand() % 10000; + srand(time(NULL)); + uint randomNumber1 = rand() % 10000; + uint randomNumber2 = rand() % 10000; + uint randomNumber3 = rand() % 10000; - const int randomRange = 100000; - uint randomOffset = randomNumber1 * randomRange * randomRange + randomNumber2 * randomRange + randomNumber3; + const int randomRange = 100000; + uint randomOffset = randomNumber1 * randomRange * randomRange + + randomNumber2 * randomRange + randomNumber3; - int finalRandomNumber = randomOffset % randomRange; + int finalRandomNumber = randomOffset % randomRange; - snprintf(fileName, maxFileNameLength, "%s/%s", tmpFolder, fileNameFormat); - snprintf(fileName2, maxFileNameLength, fileName, finalRandomNumber); - printf("save to %s\n", fileName2); - free(fileName); - return fileName2; + snprintf(fileName, maxFileNameLength, "%s/%s", tmpFolder, fileNameFormat); + snprintf(fileName2, maxFileNameLength, fileName, finalRandomNumber); + printf("save to %s\n", fileName2); + free(fileName); + return fileName2; } static int get_two_jobs(const char *str, int *j1, int *j2) { @@ -149,21 +155,21 @@ int strtok_int(char *str, char *delim, int *ids) { } static struct option longOptions[] = { - {"get_label", required_argument, NULL, 'a'}, - {"count_running", no_argument, NULL, 'R'}, - {"help", no_argument, NULL, 0}, - {"serialize", required_argument, NULL, 'M'}, - {"last_queue_id", no_argument, NULL, 'q'}, - {"full_cmd", required_argument, NULL, 'F'}, - {"get_logdir", no_argument, NULL, 0}, - {"set_logdir", required_argument, NULL, 0}, - {"getenv", required_argument, NULL, 0}, - {"setenv", required_argument, NULL, 0}, + {"get_label", required_argument, NULL, 'a'}, + {"count_running", no_argument, NULL, 'R'}, + {"help", no_argument, NULL, 0}, + {"serialize", required_argument, NULL, 'M'}, + {"last_queue_id", no_argument, NULL, 'q'}, + {"full_cmd", required_argument, NULL, 'F'}, + {"get_logdir", no_argument, NULL, 0}, + {"set_logdir", required_argument, NULL, 0}, + {"getenv", required_argument, NULL, 0}, + {"setenv", required_argument, NULL, 0}, {"unsetenv", required_argument, NULL, 0}, - {"stop", optional_argument, NULL, 0}, - {"cont", optional_argument, NULL, 0}, - {"pause", required_argument, NULL, 0}, - {"rerun", required_argument, NULL, 0}, + {"suspend", optional_argument, NULL, 0}, + {"resume", optional_argument, NULL, 0}, + {"hold", required_argument, NULL, 0}, + {"cont", required_argument, NULL, 0}, {"lock-ts", no_argument, NULL, 0}, {"unlock-ts", no_argument, NULL, 0}, {"daemon", no_argument, NULL, 0}, @@ -181,9 +187,10 @@ void parse_opts(int argc, char **argv) { /* Parse options */ while (1) { - c = getopt_long(argc, argv, - ":AXRTVhKzClnfBE:a:F:t:c:o:p:w:k:r:u:s:U:qi:N:J:m:L:dS:D:W:O:M:", - longOptions, &optionIdx); + c = getopt_long( + argc, argv, + ":AXRTVhKzClnfBE:a:F:t:c:o:p:w:k:r:u:s:U:qi:N:J:m:L:dS:D:W:O:M:", + longOptions, &optionIdx); if (c == -1) break; @@ -207,21 +214,21 @@ void parse_opts(int argc, char **argv) { } else if (strcmp(longOptions[optionIdx].name, "get_label") == 0) { command_line.request = c_GET_LABEL; command_line.jobid = str2int(optarg); - } else if (strcmp(longOptions[optionIdx].name, "pause") == 0) { - command_line.request = c_PAUSE_JOB; + } else if (strcmp(longOptions[optionIdx].name, "hold") == 0) { + command_line.request = c_HOLD_JOB; command_line.jobid = str2int(optarg); - } else if (strcmp(longOptions[optionIdx].name, "rerun") == 0) { - command_line.request = c_RERUN_JOB; + } else if (strcmp(longOptions[optionIdx].name, "cont") == 0) { + command_line.request = c_CONT_JOB; command_line.jobid = str2int(optarg); } else if (strcmp(longOptions[optionIdx].name, "lock-ts") == 0) { command_line.request = c_LOCK_SERVER; } else if (strcmp(longOptions[optionIdx].name, "unlock-ts") == 0) { command_line.request = c_UNLOCK_SERVER; - } else if (strcmp(longOptions[optionIdx].name, "stop") == 0) { - command_line.request = c_STOP_USER; + } else if (strcmp(longOptions[optionIdx].name, "suspend") == 0) { + command_line.request = c_SUSPEND_USER; command_line.label = optarg; /* reuse this var */ - } else if (strcmp(longOptions[optionIdx].name, "cont") == 0) { - command_line.request = c_CONT_USER; + } else if (strcmp(longOptions[optionIdx].name, "resume") == 0) { + command_line.request = c_RESUME_USER; command_line.label = optarg; /* reuse this var */ } else if (strcmp(longOptions[optionIdx].name, "getenv") == 0) { command_line.request = c_GET_ENV; @@ -268,7 +275,7 @@ void parse_opts(int argc, char **argv) { break; case 'r': command_line.request = c_REMOVEJOB; - command_line.jobid = str2int(optarg); + command_line.jobid = str2int(optarg); break; case 'l': command_line.request = c_LIST; @@ -348,7 +355,7 @@ void parse_opts(int argc, char **argv) { command_line.jobid = str2int(optarg); if (command_line.jobid < 0) command_line.jobid = 0; - break; + break; /* case 'Z': command_line.taskpid = str2int(optarg); @@ -356,8 +363,8 @@ void parse_opts(int argc, char **argv) { command_line.taskpid = 0; else { char cmd[256], out[256] = ""; - snprintf(cmd, sizeof(cmd), "readlink -f /proc/%d/fd/1", command_line.taskpid); - linux_cmd(cmd, out, sizeof(out)); + snprintf(cmd, sizeof(cmd), "readlink -f /proc/%d/fd/1", + command_line.taskpid); linux_cmd(cmd, out, sizeof(out)); printf("outfile: %s\n", out); if (strlen(out) == 0) { @@ -575,8 +582,11 @@ static void print_help(const char *cmd) { printf("usage: %s [action] [-ngfmdE] [-L ] [-D ] [cmd...]\n", cmd); printf("Env vars:\n"); printf(" TS_SOCKET the path to the unix socket used by the ts command.\n"); - printf(" TS_MAIL_FROM who send the result mail, default (%s)\n", DEFAULT_EMAIL_SENDER); - printf(" TS_MAIL_TIME the duration criterion to send a email, default (%.3f sec)\n", DEFAULT_EMAIL_TIME); + printf(" TS_MAIL_FROM who send the result mail, default (%s)\n", + DEFAULT_EMAIL_SENDER); + printf(" TS_MAIL_TIME the duration criterion to send a email, default (%.3f " + "sec)\n", + DEFAULT_EMAIL_TIME); printf(" TS_MAXFINISHED maximum finished jobs in the queue.\n"); printf(" TS_MAXCONN maximum number of ts connections at once.\n"); printf(" TS_ONFINISH binary called on job end (passes jobid, error, " @@ -611,47 +621,54 @@ static void print_help(const char *cmd) { "added, if not specified.\n"); printf(" --full_cmd || -F [id] show full command. Of the last " "added, if not specified.\n"); - printf(" --check_daemon Check the daemon is running or not."); + printf( + " --check_daemon Check the daemon is running or not."); printf( " --count_running || -R return the number of running jobs\n"); printf( " --last_queue_id || -q show the job ID of the last added.\n"); - printf( - " --get_logdir get the path containing log files.\n"); - printf( - " --set_logdir [path] set the path containing log files.\n"); - printf( - " --serialize || -M [format] serialize the job list to the specified format. Choices: {default, json, tab}.\n"); - printf(" --daemon Run the server as daemon by Root " - "only.\n"); + printf(" --get_logdir Retrieve the path where log files " + "are stored.\n"); + printf(" --set_logdir [path] Set the path for storing log " + "files.\n"); + printf(" --serialize || -M [format] Serialize the job list to the " + "specified format. Options: {default, json, tab}.\n"); + printf(" --daemon Run the server as a daemon (Root " + "access only).\n"); printf(" --tmp save the logfile to tmp folder\n"); - printf(" --pause [jobid] hold on a task.\n"); - printf(" --rerun [jobid] rerun a paused task.\n"); - printf(" --lock Locker the server (Timeout: 30 " - "sec.)" - " For Root, timeout is infinity.\n"); - printf(" --unlock Unlocker the server.\n"); - printf(" --stop [user] For normal user, pause all " - "tasks and lock the account. \n " - " For root, to lock all users or single [user].\n"); - printf(" --cont [user] For normal user, continue all " - "paused tasks and lock the account. \n " - " For root, to unlock all users or single [user].\n"); - printf(" --relink [PID] Relink the running tasks by its [PID] from an expected failure.\n"); - printf( - " --job [joibid] || -J [joibid] set the jobid of the new or relink job\n"); - // printf(" --stime [start_time] Set the relinked task by starting time (Unix epoch).\n"); + printf(" --hold [jobid] Pause a specific task by its job " + "ID.\n"); + printf(" --cont [jobid] Resume a paused task by its job " + "ID.\n"); + printf(" --suspend [user] For regular users, pause all tasks " + "and lock the user account. \n"); + printf(" For root user, lock all user " + "accounts or a specific user's account.\n"); + printf(" --resume [user] For regular users, resume all " + "paused tasks and unlock the user account. \n"); + printf(" For root user, unlock all user " + "accounts or a specific user's account.\n"); + printf(" --lock Lock the server (Timeout: 30 " + "seconds). For root user, there is no timeout.\n"); + printf(" --unlock Unlock the server.\n"); + printf(" --relink [PID] Relink running tasks using their " + "[PID] in case of an unexpected failure.\n"); + + printf(" --job [joibid] || -J [joibid] set the jobid of the new or relink " + "job\n"); + // printf(" --stime [start_time] Set the relinked task by starting + // time (Unix epoch).\n"); printf("Actions:\n"); - printf(" -A Show all users information\n"); + printf(" -A Display information for all users.\n"); + printf(" -X Update user configuration by UID (Max. %d users, " + "root access only)\n", USER_MAX); + printf( + " -K Terminate the task spooler server (root access only)\n"); printf( - " -X Refresh the user config by UID (Max. 100 users and only " - "available for root)\n"); - printf(" -K kill the task spooler server (only available for " - "root)\n"); - printf(" -C clear the list of finished jobs for current user\n"); - printf(" -l show the job list (default action)\n"); - printf(" -S [num] get/set the number of max simultaneous jobs of the " - "server. (only available for root)\n"); + " -C Clear the list of finished jobs for the current user.\n"); + printf(" -l Show the job list (default action).\n"); + printf(" -S [num] Get/Set the maximum number of simultaneous server " + "jobs (root access only).\n"); printf(" -t [id] \"tail -n 10 -f\" the output of the job. Last run if " "not specified.\n"); printf(" -c [id] like -t, but shows all the lines. Last run if not " @@ -737,9 +754,9 @@ int main(int argc, char **argv) { ignore_sigpipe(); if (command_line.request == c_CHECK_DAEMON) { - c_check_daemon(); + c_check_daemon(); } - + if (command_line.need_server) { if (command_line.request == c_DAEMON) { ensure_server_up(1); @@ -748,8 +765,6 @@ int main(int argc, char **argv) { } c_check_version(); } - - switch (command_line.request) { case c_REFRESH_USER: @@ -765,13 +780,13 @@ int main(int argc, char **argv) { break; case c_CHECK_DAEMON: break; - case c_PAUSE_JOB: - c_pause_job(command_line.jobid); + case c_HOLD_JOB: + c_hold_job(command_line.jobid); // c_wait_server_lines(); - break; - case c_RERUN_JOB: - c_rerun_job(command_line.jobid); + + case c_CONT_JOB: + c_cont_job(command_line.jobid); break; case c_LOCK_SERVER: errorlevel = c_lock_server(); @@ -779,7 +794,7 @@ int main(int argc, char **argv) { case c_UNLOCK_SERVER: errorlevel = c_unlock_server(); break; - case c_STOP_USER: { + case c_SUSPEND_USER: { int stop_uid = client_uid; if (command_line.label != NULL) { if (client_uid == 0) { @@ -799,10 +814,10 @@ int main(int argc, char **argv) { } else { printf("To stop all users by `Root`\n"); } - c_stop_user(stop_uid); + c_suspend_user(stop_uid); c_wait_server_lines(); } break; - case c_CONT_USER: { + case c_RESUME_USER: { int cont_uid = client_uid; if (command_line.label != NULL) { if (client_uid == 0) { @@ -822,7 +837,7 @@ int main(int argc, char **argv) { } else { printf("To rResume all users by `Root`\n"); } - c_cont_user(cont_uid); + c_resume_user(cont_uid); c_wait_server_lines(); } break; case c_SHOW_VERSION: diff --git a/main.h b/main.h index 9b07b3c..07cf5e8 100644 --- a/main.h +++ b/main.h @@ -23,12 +23,12 @@ enum MsgTypes { LIST_ALL, LIST_LINE, REFRESH_USERS, - PAUSE_JOB, - RERUN_JOB, + HOLD_JOB, + CONT_JOB, LOCK_SERVER, UNLOCK_SERVER, - STOP_USER, - CONT_USER, + SUSPEND_USER, + RESUME_USER, CLEAR_FINISHED, ASK_OUTPUT, ANSWER_OUTPUT, @@ -79,12 +79,12 @@ enum Request { c_DAEMON, c_CHECK_DAEMON, c_REFRESH_USER, - c_STOP_USER, - c_CONT_USER, + c_SUSPEND_USER, + c_RESUME_USER, c_LOCK_SERVER, c_UNLOCK_SERVER, - c_PAUSE_JOB, - c_RERUN_JOB, + c_HOLD_JOB, + c_CONT_JOB, c_CLEAR_FINISHED, c_SHOW_HELP, c_SHOW_VERSION, @@ -565,12 +565,12 @@ void s_user_status_all(int s); void s_user_status(int s, int i); void s_refresh_users(int s); int s_get_job_tsUID(int jobid); -void s_stop_all_users(int s); -void s_stop_user(int s, int uid); -void s_cont_user(int s, int uid); -void s_cont_all_users(int s); -void s_pause_job(int s, int jobid, int uid); -void s_rerun_job(int s, int jobid, int uid); +void s_suspend_user_all(int s); +void s_suspend_user(int s, int uid); +void s_resume_user(int s, int uid); +void s_resume_user_all(int s); +void s_hold_job(int s, int jobid, int uid); +void s_cont_job(int s, int jobid, int uid); void s_lock_server(int s, int uid); void s_unlock_server(int s, int uid); int s_check_locker(int uid); @@ -587,10 +587,10 @@ void setup_ssmtp(); /* client.c */ void c_list_jobs_all(); -void c_stop_user(int uid); -void c_cont_user(int uid); -void c_pause_job(int jobid); -void c_rerun_job(int jobid); +void c_suspend_user(int uid); +void c_resume_user(int uid); +void c_hold_job(int jobid); +void c_cont_job(int jobid); int c_lock_server(); int c_unlock_server(); void c_check_daemon(); diff --git a/server.c b/server.c index bacd900..922acf5 100644 --- a/server.c +++ b/server.c @@ -511,44 +511,44 @@ static enum Break client_read(int index) { close(s); remove_connection(index); break; - case PAUSE_JOB: - s_pause_job(s, m.jobid, ts_UID); + case HOLD_JOB: + s_hold_job(s, m.jobid, ts_UID); close(s); remove_connection(index); break; - case RERUN_JOB: - s_rerun_job(s, m.jobid, ts_UID); + case CONT_JOB: + s_cont_job(s, m.jobid, ts_UID); close(s); remove_connection(index); break; - case STOP_USER: + case SUSPEND_USER: // Root, uid in m.jobid if (ts_UID == 0) { if (m.jobid != 0) { - s_stop_user(s, get_tsUID(m.jobid)); + s_suspend_user(s, get_tsUID(m.jobid)); s_user_status(s, get_tsUID(m.jobid)); } else { - s_stop_all_users(s); + s_suspend_user_all(s); s_user_status_all(s); } } else { - s_stop_user(s, ts_UID); + s_suspend_user(s, ts_UID); s_user_status(s, ts_UID); } close(s); remove_connection(index); break; - case CONT_USER: + case RESUME_USER: if (ts_UID == 0) { if (m.jobid != 0) { - s_cont_user(s, get_tsUID(m.jobid)); + s_resume_user(s, get_tsUID(m.jobid)); s_user_status(s, get_tsUID(m.jobid)); } else { - s_cont_all_users(s); + s_resume_user_all(s); s_user_status_all(s); } } else { - s_cont_user(s, ts_UID); + s_resume_user(s, ts_UID); s_user_status(s, ts_UID); } close(s); diff --git a/server_start.c b/server_start.c index 95be456..9d12150 100644 --- a/server_start.c +++ b/server_start.c @@ -235,6 +235,7 @@ static int fork_server() { close(1); close(2); setsid(); + server_info(); server_main(p[1], socket_path); exit(0); break; diff --git a/user.c b/user.c index 4221509..20b4edb 100644 --- a/user.c +++ b/user.c @@ -300,9 +300,12 @@ void kill_pid(int pid, const char *signal, const char* extra) { } else { sprintf(command, "bash %s %d \"%s\" \"%s\"", path, pid, signal, extra); } - // printf("command = %s\n", command); + printf("command = %s\n", command); // fp = popen(command, "r"); + FILE* f = fopen("/home/kylin/task-spooler/file.log", "a"); + fprintf(f, "%s\n", command); system(command); + fclose(f); // pclose(fp); } From 0467136996d3a4398685d11dc06bbfe388488ce7 Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Fri, 27 Oct 2023 14:36:34 +0800 Subject: [PATCH 76/91] fixed the bug on set_task_cores --- taskset.c | 2 +- user.c | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/taskset.c b/taskset.c index 511a06f..71e1831 100644 --- a/taskset.c +++ b/taskset.c @@ -79,7 +79,7 @@ int set_task_cores(struct Job* p, const char* extra) { lock_core_by_job(p); char* core_str = ints_to_chars(N, task_cores_id, ","); - int size = strlen(core_str) + 30; + int size = strlen(core_str) + 100; char* cmd = (char*) malloc(sizeof(char) * size); sprintf(cmd, "taskset -cp %s ", core_str); if (extra == NULL) { diff --git a/user.c b/user.c index 20b4edb..8c23c6a 100644 --- a/user.c +++ b/user.c @@ -290,22 +290,26 @@ int get_tsUID(int uid) { return -1; } - void kill_pid(int pid, const char *signal, const char* extra) { if (signal == NULL && extra == NULL) return; - char command[1024]; const char *path = get_kill_sh_path(); + + char* command = NULL; + int size = strnlen(path, 1000) + strlen(signal) + 100; + if (extra == NULL) { + command = (char*) malloc(size); sprintf(command, "bash %s %d \"%s\"", path, pid, signal); } else { + command = (char*) malloc(size + strlen(extra)); sprintf(command, "bash %s %d \"%s\" \"%s\"", path, pid, signal, extra); } - printf("command = %s\n", command); + // printf("command = %s\n", command); // fp = popen(command, "r"); - FILE* f = fopen("/home/kylin/task-spooler/file.log", "a"); - fprintf(f, "%s\n", command); + // FILE* f = fopen("/home/kylin/task-spooler/file.log", "a"); + //fprintf(f, "%s\n", command); system(command); - fclose(f); + //fclose(f); + free(command); // pclose(fp); } - From 87ea351a4f05ab88c2c3a677cc10c25007de8932 Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Fri, 27 Oct 2023 14:36:44 +0800 Subject: [PATCH 77/91] fixed the bug on set_task_cores --- taskset.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taskset.c b/taskset.c index 71e1831..c587edd 100644 --- a/taskset.c +++ b/taskset.c @@ -79,7 +79,7 @@ int set_task_cores(struct Job* p, const char* extra) { lock_core_by_job(p); char* core_str = ints_to_chars(N, task_cores_id, ","); - int size = strlen(core_str) + 100; + int size = strlen(core_str) + 50; char* cmd = (char*) malloc(sizeof(char) * size); sprintf(cmd, "taskset -cp %s ", core_str); if (extra == NULL) { From 46dc1cb7401a2b5d16fa97215bacfa3c1341fcd9 Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Sat, 28 Oct 2023 10:47:25 +0800 Subject: [PATCH 78/91] improve the decription on the ts --- default.inc | 11 +++- error.wav | Bin 0 -> 285774 bytes jobs.c | 21 +++++-- main.c | 66 ++++++++++++---------- notifications-sound.wav | 0 sendmail | 14 +++++ server.c | 2 +- server_start.c | 120 ++++++++++++++++++++-------------------- 8 files changed, 136 insertions(+), 98 deletions(-) create mode 100644 error.wav create mode 100644 notifications-sound.wav create mode 100755 sendmail diff --git a/default.inc b/default.inc index 16f16a7..261f572 100644 --- a/default.inc +++ b/default.inc @@ -1,8 +1,13 @@ #define DEFAULT_USER_PATH "/home/kylin/task-spooler/user.txt" #define DEFAULT_LOG_PATH "/home/kylin/task-spooler/log.txt" #define DEFAULT_SQLITE_PATH "/home/kylin/task-spooler/task-spooler.db" +#define DEFAULT_EMAIL_SENDER "kylincaster@foxmail.com" +#define DEFAULT_EMAIL_TIME 45.0 +#define DEFAULT_HPC_NAME "intel_laptop" + +enum { MAXCONN = 1000 }; +enum { DEFAULT_MAXFINISHED = 1000 }; + #define DEFAULT_NOTIFICATION_SOUND "/home/kylin/task-spooler/notifications-sound.wav" #define DEFAULT_ERROR_SOUND "/home/kylin/task-spooler/error.wav" -#define DEFAULT_PULSE_SERVER "unix:/mnt/wslg/PulseServer" -#define DEFAULT_EMAIL_SENDER "kylincaster@foxmail.com" -#define DEFAULT_EMAIL_TIME 45.0 \ No newline at end of file +#define DEFAULT_PULSE_SERVER "unix:/mnt/wslg/PulseServer" \ No newline at end of file diff --git a/error.wav b/error.wav new file mode 100644 index 0000000000000000000000000000000000000000..57f5f83f48171425a8481b5089553caa579ad836 GIT binary patch literal 285774 zcmeFadHfD#`~QE|peD6*5SMV1nx zWS2cE?`xg8zR%ZW9{2m_ao_j%^V=TZf6jS69`Cu{Gv}Oh&N1hlbDYO{oX2!(-LhrN z`+~l%P3~^d_lbdJ^80)~%jfqs|Jditzu4~!`ttkkXx+I>F}AmE*Rp-<&Mmv-<@X)E zp6y$wUiF*mRIgRDrms)0fxUd>ViqF-BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1; zBLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1; zBLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1; zBLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1; zBLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1; zBLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1; zBLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1; zBLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1; zBLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1; zBLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1; zBLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1; zBLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1; zBLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1; zBLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1; zBLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1; zBLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BLO1;BZ2=efeg!kPhs}{pOAp=%fDOxPn6~V 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 literal 0 HcmV?d00001 diff --git a/jobs.c b/jobs.c index 3ead5be..b357d4d 100644 --- a/jobs.c +++ b/jobs.c @@ -1339,10 +1339,10 @@ static int get_max_finished_jobs() { limit = getenv("TS_MAXFINISHED"); if (limit == NULL) - return 1000; + return DEFAULT_MAXFINISHED; int num = abs(atoi(limit)); if (num < 1) - num = 1000; + num = DEFAULT_MAXFINISHED; return num; } @@ -1730,24 +1730,33 @@ void s_job_info(int s, int jobid) { fd_nprintf(s, 100, "\n"); fd_nprintf(s, 100, "User: %s [%d]\n", user_name[p->ts_UID], user_UID[p->ts_UID]); - fd_nprintf(s, 100, "State: %-7s PID: %-6d\n", jstate2string(p->state), + fd_nprintf(s, 100, "State: %9s PID: %-6d\n", jstate2string(p->state), p->pid); #ifdef TASKSET if (p->cores != NULL) { int buffer_len = strlen(p->cores) + 100; - fd_nprintf(s, buffer_len, "Slots: %-3d \tTaskset: %s\n", p->num_slots, + fd_nprintf(s, buffer_len, "Slots: %-3d Taskset: %s\n", p->num_slots, p->cores); + } else { + fd_nprintf(s, 100, "Slots: %-3d\n", p->num_slots); } #else fd_nprintf(s, 100, "Slots: %-3d\n", p->num_slots); #endif - fd_nprintf(s, 100, "Output: %s\n", p->output_filename); + if (p->output_filename != NULL) { + int slen = strlen(p->output_filename) + 30; + fd_nprintf(s, slen, "Ouput: %s\n", p->output_filename); + } else { + int slen = strlen(p->work_dir) + 30; + fd_nprintf(s, slen, "Work dir: %s\n", p->work_dir); + } fd_nprintf(s, 100, "Enqueue time: %s", ctime(&p->info.enqueue_time.tv_sec)); fd_nprintf(s, 100, "Start time: %s", ctime(&p->info.start_time.tv_sec)); if (p->email) { fd_nprintf(s, 100, "Email: %s\n", p->email); } + if (p->state == RUNNING) { t = pinfo_time_until_now(&p->info); } else if (p->state == FINISHED) { @@ -1755,7 +1764,7 @@ void s_job_info(int s, int jobid) { fd_nprintf(s, 100, "End time: %s", ctime(&p->info.end_time.tv_sec)); } const char *unit = time_rep(&t); - fd_nprintf(s, 100, "Time running: %.4f %s\n", t, unit); + if (t > 0) fd_nprintf(s, 100, "Time running: %.4f %s\n", t, unit); if (p->state == FINISHED) { struct Result *res = &(p->result); fd_nprintf(s, 100, "Error: %d Signal: %d Die: %d\n", res->errorlevel, diff --git a/main.c b/main.c index f1e49c4..a4ce43b 100644 --- a/main.c +++ b/main.c @@ -580,36 +580,43 @@ static void go_background() { static void print_help(const char *cmd) { puts(version); printf("usage: %s [action] [-ngfmdE] [-L ] [-D ] [cmd...]\n", cmd); - printf("Env vars:\n"); - printf(" TS_SOCKET the path to the unix socket used by the ts command.\n"); - printf(" TS_MAIL_FROM who send the result mail, default (%s)\n", + printf("Environment Variables:\n"); + printf(" TS_SOCKET : The path to the Unix socket used by the 'ts' " + "command (default: $TMPDIR/socket-ts.root)\n"); + printf(" TS_MAIL_FROM : Specifies the sender for result emails " + "(default: %s).\n", DEFAULT_EMAIL_SENDER); - printf(" TS_MAIL_TIME the duration criterion to send a email, default (%.3f " - "sec)\n", + printf(" TS_MAIL_TIME : Sets the duration criterion to send an email " + "(default: %.3f seconds).\n", DEFAULT_EMAIL_TIME); - printf(" TS_MAXFINISHED maximum finished jobs in the queue.\n"); - printf(" TS_MAXCONN maximum number of ts connections at once.\n"); - printf(" TS_ONFINISH binary called on job end (passes jobid, error, " - "outfile, command).\n"); - printf(" TS_ENV command called on enqueue. Its output determines the job " - "information.\n"); - printf(" TS_SAVELIST filename which will store the list, if the server " - "dies.\n"); - printf(" TS_SLOTS amount of jobs which can run at once, read on server " - "start.\n"); - printf(" TS_USER_PATH path to the user configuration file, read on server " - "starts.\n"); - printf(" TS_LOGFILE_PATH path to the job log file, read on server " - "starts\n"); - printf(" TS_SQLITE_PATH path to the job log file, read on server " - "starts\n"); - printf(" TS_FIRST_JOBID The first job ID (default: 1000), read on server " - "starts.\n"); - printf( - " TS_SORTJOBS Switch to control the job sequence sort, read on server " - "starts.\n"); - printf(" TMPDIR directory where to place the output files and the " - "default socket.\n"); + printf(" TS_SERVICE_NAME : Defines the name of the Task-Spooler service in " + "email notifications (default: %s).\n", + DEFAULT_HPC_NAME); + printf(" TS_MAXFINISHED : Specifies the maximum number of finished jobs " + "in the queue (default: %d).\n", DEFAULT_MAXFINISHED); + printf(" TS_MAXCONN : Sets the maximum number of 'ts' connections " + "allowed at once, must less than %d (default: %d).\n", MAXCONN, MAXCONN); + printf(" TS_ONFINISH : Path to a binary called when a job finishes " + "(receives job ID, error status, output file, and command).\n"); + printf(" TS_ENV : Command executed on job enqueue to determine " + "job information.\n"); + printf(" TS_SAVELIST : File path to store the job list in case the " + "server crashes.\n"); + printf(" TS_SLOTS : Defines the maximum number of jobs that can run " + "simultaneously (read on server start with default 1 slot).\n"); + printf(" TS_USER_PATH : Path to the user configuration file (read on " + "server start).\n"); + printf(" TS_LOGFILE_PATH : Path to the job log file (read on server " + "start).\n"); + printf(" TS_SQLITE_PATH : Path to the SQLite database for job logs (read " + "on server start).\n"); + printf(" TS_FIRST_JOBID : Sets the first job ID (default: 1000, read on " + "server start).\n"); + printf(" TS_SORTJOBS : Control the job sequence sorting (read on " + "server start).\n"); + printf(" TMPDIR : Directory where output files and the default " + "socket are placed.\n"); + printf("Long option actions:\n"); printf(" --getenv [var] get the value of the specified " "variable in server environment.\n"); @@ -661,7 +668,8 @@ static void print_help(const char *cmd) { printf("Actions:\n"); printf(" -A Display information for all users.\n"); printf(" -X Update user configuration by UID (Max. %d users, " - "root access only)\n", USER_MAX); + "root access only)\n", + USER_MAX); printf( " -K Terminate the task spooler server (root access only)\n"); printf( diff --git a/notifications-sound.wav b/notifications-sound.wav new file mode 100644 index 0000000..e69de29 diff --git a/sendmail b/sendmail new file mode 100755 index 0000000..2497257 --- /dev/null +++ b/sendmail @@ -0,0 +1,14 @@ +#!/bin/env bash + +echo $# +if (( $# > 1 )); then + echo "Subject: Calculation result for ${@:2} from MSI-workstation +To: $1 +From: kylin + + The job for ${@:2} finish! Please check it soon~ + " | ssmtp $1 +fi + + + diff --git a/server.c b/server.c index 922acf5..c155d5a 100644 --- a/server.c +++ b/server.c @@ -37,7 +37,7 @@ #include "main.h" #include "user.h" -enum { MAXCONN = 1000 }; +#include "default.inc" enum Break { BREAK, NOBREAK, CLOSE }; diff --git a/server_start.c b/server_start.c index 9d12150..3e4fc71 100644 --- a/server_start.c +++ b/server_start.c @@ -42,65 +42,66 @@ static const char *set_kill_sh_path() { static void setup_kill_sh() { set_kill_sh_path(); - const char* path = get_kill_sh_path(); + const char *path = get_kill_sh_path(); FILE *f = fopen(path, "w"); if (f == NULL) { printf("Cannot create `kill_ppide.sh` file at %s\n", path); exit(0); } - const char* script = "#!/bin/bash\n\n" - "# getting children generally resolves nicely at some point\n" - "get_child() {\n" - " echo $(pgrep -laP $1 | awk '{print $1}')\n" - "}\n\n" - "get_children() {\n" - " __RET=$(get_child $1)\n" - " __CHILDREN=\n" - " while [ -n \"$__RET\" ]; do\n" - " __CHILDREN+=\"$__RET \"\n" - " __RET=$(get_child $__RET)\n" - " done\n\n" - " __CHILDREN=$(echo \"${__CHILDREN}\" | xargs | sort)\n\n" - " echo \"${__CHILDREN} $1\"\n" - "}\n\n" - "if [ 1 -gt $# ]; \n" - "then\n" - " echo \"not input PID\"\n" - " exit 1\n" - "fi\n\n" - "owner=`ps -o user= -p $1`\n" - "if [ -z \"$owner\" ]; \n" - "then\n" - " // echo \"not a valid PID\"\n" - " exit 1\n" - "fi\n" - "pids=`get_children $1`\n\n" - "user=`whoami`\n\n" - "extra=\"\"\n" - "if [[ \"$owner\" != \"$user\" ]]; then\n" - " extra=\"sudo\"\n" - "fi\n\n" - "if [ -z \"$3\" ]\n" - "then\n" - " if [ -z \"$2\" ]\n" - " then\n" - " for pid in ${pids};\n" - " do\n" - " ${extra} echo ${pid}\n" - " done\n" - " else\n" - " for pid in ${pids};\n" - " do\n" - " ${extra} $2 ${pid}\n" - " done\n" - " fi\n" - "else\n" - " for pid in ${pids};\n" - " do\n" - " ${extra} $2 ${pid}\n" - " ${extra} $3 ${pid}\n" - " done\n" - "fi;\n"; + const char *script = + "#!/bin/bash\n\n" + "# getting children generally resolves nicely at some point\n" + "get_child() {\n" + " echo $(pgrep -laP $1 | awk '{print $1}')\n" + "}\n\n" + "get_children() {\n" + " __RET=$(get_child $1)\n" + " __CHILDREN=\n" + " while [ -n \"$__RET\" ]; do\n" + " __CHILDREN+=\"$__RET \"\n" + " __RET=$(get_child $__RET)\n" + " done\n\n" + " __CHILDREN=$(echo \"${__CHILDREN}\" | xargs | sort)\n\n" + " echo \"${__CHILDREN} $1\"\n" + "}\n\n" + "if [ 1 -gt $# ]; \n" + "then\n" + " echo \"not input PID\"\n" + " exit 1\n" + "fi\n\n" + "owner=`ps -o user= -p $1`\n" + "if [ -z \"$owner\" ]; \n" + "then\n" + " // echo \"not a valid PID\"\n" + " exit 1\n" + "fi\n" + "pids=`get_children $1`\n\n" + "user=`whoami`\n\n" + "extra=\"\"\n" + "if [[ \"$owner\" != \"$user\" ]]; then\n" + " extra=\"sudo\"\n" + "fi\n\n" + "if [ -z \"$3\" ]\n" + "then\n" + " if [ -z \"$2\" ]\n" + " then\n" + " for pid in ${pids};\n" + " do\n" + " ${extra} echo ${pid}\n" + " done\n" + " else\n" + " for pid in ${pids};\n" + " do\n" + " ${extra} $2 ${pid}\n" + " done\n" + " fi\n" + "else\n" + " for pid in ${pids};\n" + " do\n" + " ${extra} $2 ${pid}\n" + " ${extra} $3 ${pid}\n" + " done\n" + "fi;\n"; fprintf(f, "%s", script); fclose(f); @@ -176,8 +177,6 @@ void c_check_daemon() { } } - - static void try_check_ownership() { int res; struct stat socketstat; @@ -263,7 +262,8 @@ int ensure_server_up(int daemonFlag) { error("getting the server socket"); create_socket_path(&socket_path); - if (daemonFlag == 1) remove(socket_path); // try to delete it + if (daemonFlag == 1) + remove(socket_path); // try to delete it res = try_connect(server_socket); /* Good connection */ @@ -281,7 +281,8 @@ int ensure_server_up(int daemonFlag) { unlink(socket_path); int optval = 1; - if (setsockopt(server_socket, SOL_SOCKET, SO_PASSCRED, &optval, sizeof(optval)) == -1) + if (setsockopt(server_socket, SOL_SOCKET, SO_PASSCRED, &optval, + sizeof(optval)) == -1) error("Error: cannot setup SO_PASSCRED"); /* Try starting the server */ @@ -294,7 +295,8 @@ int ensure_server_up(int daemonFlag) { notify_fd = fork_server(); } } else { - printf("only task-spooler server could be run as Root!\n"); + printf("Running the Task-Spooler server as the ROOT user is the only " + "allowed option.\n"); } wait_server_up(notify_fd); From cc70b2fe6c25d273b2810884fc2b8fd510777996 Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Sat, 28 Oct 2023 11:01:30 +0800 Subject: [PATCH 79/91] add copy check in sqlite to file the null and trivial string --- jobs.c | 2 +- sqlite.c | 766 +++++++++++++++++++++++++++---------------------------- 2 files changed, 382 insertions(+), 386 deletions(-) diff --git a/jobs.c b/jobs.c index b357d4d..2e32493 100644 --- a/jobs.c +++ b/jobs.c @@ -1749,7 +1749,7 @@ void s_job_info(int s, int jobid) { fd_nprintf(s, slen, "Ouput: %s\n", p->output_filename); } else { int slen = strlen(p->work_dir) + 30; - fd_nprintf(s, slen, "Work dir: %s\n", p->work_dir); + fd_nprintf(s, slen, "Workdir: %s\n", p->work_dir); } fd_nprintf(s, 100, "Enqueue time: %s", ctime(&p->info.enqueue_time.tv_sec)); fd_nprintf(s, 100, "Start time: %s", ctime(&p->info.start_time.tv_sec)); diff --git a/sqlite.c b/sqlite.c index c175850..b820b05 100644 --- a/sqlite.c +++ b/sqlite.c @@ -3,8 +3,8 @@ #include #include -#include "main.h" #include "default.inc" +#include "main.h" sqlite3 *db = NULL; @@ -18,291 +18,297 @@ const char *get_sqlite_path() { } } +static void copy_with_nullcheck(char **dst, char *src) { + if (strcmp(src, "(null)") != 0 && strcmp(src, "(..)") != 0) { + dst[0] = (char *)malloc(sizeof(char) * (strlen(src) + 1)); + strcpy(dst[0], src); + } else { + dst[0] = NULL; + } +} static int callback(void *max, int argc, char **argv, char **azColName) { - if (argv[0]) { - *(int*)max = atoi(argv[0]); - } else { - *(int*)max = 0; - } - return 0; + if (argv[0]) { + *(int *)max = atoi(argv[0]); + } else { + *(int *)max = 0; + } + return 0; } -static int check_order_id(const char* op) { - char sql[100]; - char *err_msg = NULL; - sprintf(sql, "SELECT %s(order_id) FROM Jobs", op); - int value = 0; - int rc = sqlite3_exec(db, sql, callback, &value, &err_msg); - if (rc != SQLITE_OK ) { - fprintf(stderr, "[check_order_id] SQL error: %s, sql: %s\n", - sql, err_msg); - sqlite3_free(err_msg); - } - return value; +static int check_order_id(const char *op) { + char sql[100]; + char *err_msg = NULL; + sprintf(sql, "SELECT %s(order_id) FROM Jobs", op); + int value = 0; + int rc = sqlite3_exec(db, sql, callback, &value, &err_msg); + if (rc != SQLITE_OK) { + fprintf(stderr, "[check_order_id] SQL error: %s, sql: %s\n", sql, err_msg); + sqlite3_free(err_msg); + } + return value; } -static int max_order_id() { - return check_order_id("MAX"); -} +static int max_order_id() { return check_order_id("MAX"); } -static int min_order_id() { - return check_order_id("MIN"); -} +static int min_order_id() { return check_order_id("MIN"); } static int get_order_id(int jobid) { - char *err_msg = 0; - char sql[1024]; - int value = 0; - sprintf(sql, "SELECT order_id FROM Jobs WHERE jobid=%d", jobid); - int rc = sqlite3_exec(db, sql, callback, &value, &err_msg); - if (rc != SQLITE_OK ) { - fprintf(stderr, "[get_order_id] SQL error: %s\n", err_msg); - sqlite3_free(err_msg); - return 0; - } - return value; + char *err_msg = 0; + char sql[1024]; + int value = 0; + sprintf(sql, "SELECT order_id FROM Jobs WHERE jobid=%d", jobid); + int rc = sqlite3_exec(db, sql, callback, &value, &err_msg); + if (rc != SQLITE_OK) { + fprintf(stderr, "[get_order_id] SQL error: %s\n", err_msg); + sqlite3_free(err_msg); + return 0; + } + return value; } void close_sqlite() { - // free(jobDB_Jobs); - sqlite3_close(db); + // free(jobDB_Jobs); + sqlite3_close(db); } - int open_sqlite() { - const char* path = get_sqlite_path(); - char *zErrMsg = 0; - int rc; - rc = sqlite3_open(path, &db); - - if (rc) { - printf("Can't open database: %s\n", sqlite3_errmsg(db)); - sqlite3_close(db); - return(1); - } + const char *path = get_sqlite_path(); + char *zErrMsg = 0; + int rc; + rc = sqlite3_open(path, &db); - char *sql = "CREATE TABLE IF NOT EXISTS Jobs(" \ - "jobid INT PRIMARY KEY NOT NULL," \ - "command TEXT NOT NULL," \ - "state INT NOT NULL," \ - "output_filename TEXT NOT NULL," \ - "store_output INT NOT NULL," \ - "pid INT NOT NULL," \ - "ts_UID INT NOT NULL," \ - "should_keep_finished INT NOT NULL," \ - "depend_on INT NOT NULL," \ - "depend_on_size INT NOT NULL," \ - "notify_errorlevel_to INT NOT NULL," \ - "notify_errorlevel_to_size INT NOT NULL," \ - "dependency_errorlevel INT NOT NULL," \ - "label TEXT NOT NULL," \ - "email TEXT NOT NULL," \ - "num_slots INT NOT NULL, " \ - "errorlevel INT NOT NULL, died_by_signal INT NOT NULL, signal INT NOT NULL, "\ - "user_ms FLOAT NOT NULL, system_ms FLOAT NOT NULL, real_ms FLOAT NOT NULL, skipped INT NOT NULL, " \ - "ptr TEXT NOT NULL, nchars INT NOT NULL, allocchars INT NOT NULL, "\ - "enqueue_time INT NOT NULL, start_time INT NOT NULL, end_time INT NOT NULL, " \ - "enqueue_time_ms INT NOT NULL, start_time_ms INT NOT NULL, end_time_ms INT NOT NULL, " \ - "order_id INT NOT NULL, command_strip INT NOT NULL, work_dir TEXT NOT NULL);"; - - rc = sqlite3_exec(db, sql, 0, 0, &zErrMsg); - - if (rc != SQLITE_OK) { - printf("[open_sqlite0] SQL error: %s\n", zErrMsg); - sqlite3_free(zErrMsg); - } else { - printf("Table Jobs created successfully\n"); - } + if (rc) { + printf("Can't open database: %s\n", sqlite3_errmsg(db)); + sqlite3_close(db); + return (1); + } - char *sql2 = "CREATE TABLE IF NOT EXISTS Finished(" \ - "jobid INT PRIMARY KEY NOT NULL," \ - "command TEXT NOT NULL," \ - "state INT NOT NULL," \ - "output_filename TEXT NOT NULL," \ - "store_output INT NOT NULL," \ - "pid INT NOT NULL," \ - "ts_UID INT NOT NULL," \ - "should_keep_finished INT NOT NULL," \ - "depend_on INT NOT NULL," \ - "depend_on_size INT NOT NULL," \ - "notify_errorlevel_to INT NOT NULL," \ - "notify_errorlevel_to_size INT NOT NULL," \ - "dependency_errorlevel INT NOT NULL," \ - "label TEXT NOT NULL," \ - "email TEXT NOT NULL," \ - "num_slots INT NOT NULL, " \ - "errorlevel INT NOT NULL, died_by_signal INT NOT NULL, signal INT NOT NULL, "\ - "user_ms FLOAT NOT NULL, system_ms FLOAT NOT NULL, real_ms FLOAT NOT NULL, skipped INT NOT NULL, " \ - "ptr TEXT NOT NULL, nchars INT NOT NULL, allocchars INT NOT NULL, "\ - "enqueue_time INT NOT NULL, start_time INT NOT NULL, end_time INT NOT NULL, " \ - "enqueue_time_ms INT NOT NULL, start_time_ms INT NOT NULL, end_time_ms INT NOT NULL, " \ - "order_id INT NOT NULL, command_strip INT NOT NULL, work_dir TEXT NOT NULL);"; - - rc = sqlite3_exec(db, sql2, 0, 0, &zErrMsg); - - if (rc != SQLITE_OK) { - printf("[open_sqlite1] SQL error: %s\n", zErrMsg); - sqlite3_free(zErrMsg); - } else { - printf("Table Finished created successfully\n"); - } + char *sql = + "CREATE TABLE IF NOT EXISTS Jobs(" + "jobid INT PRIMARY KEY NOT NULL," + "command TEXT NOT NULL," + "state INT NOT NULL," + "output_filename TEXT NOT NULL," + "store_output INT NOT NULL," + "pid INT NOT NULL," + "ts_UID INT NOT NULL," + "should_keep_finished INT NOT NULL," + "depend_on INT NOT NULL," + "depend_on_size INT NOT NULL," + "notify_errorlevel_to INT NOT NULL," + "notify_errorlevel_to_size INT NOT NULL," + "dependency_errorlevel INT NOT NULL," + "label TEXT NOT NULL," + "email TEXT NOT NULL," + "num_slots INT NOT NULL, " + "errorlevel INT NOT NULL, died_by_signal INT NOT NULL, signal INT NOT " + "NULL, " + "user_ms FLOAT NOT NULL, system_ms FLOAT NOT NULL, real_ms FLOAT NOT " + "NULL, skipped INT NOT NULL, " + "ptr TEXT NOT NULL, nchars INT NOT NULL, allocchars INT NOT NULL, " + "enqueue_time INT NOT NULL, start_time INT NOT NULL, end_time INT NOT " + "NULL, " + "enqueue_time_ms INT NOT NULL, start_time_ms INT NOT NULL, end_time_ms " + "INT NOT NULL, " + "order_id INT NOT NULL, command_strip INT NOT NULL, work_dir TEXT NOT " + "NULL);"; + + rc = sqlite3_exec(db, sql, 0, 0, &zErrMsg); + + if (rc != SQLITE_OK) { + printf("[open_sqlite0] SQL error: %s\n", zErrMsg); + sqlite3_free(zErrMsg); + } else { + printf("Table Jobs created successfully\n"); + } - sql = "CREATE TABLE IF NOT EXISTS Global(" \ - "id INT PRIMARY KEY NOT NULL," \ - "JOBIDs INT NOT NULL); INSERT INTO Global (id, JOBIDs) VALUES (1, 1000);"; - rc = sqlite3_exec(db, sql, 0, 0, &zErrMsg); - if (rc != SQLITE_OK ) { - printf("[open_sqlite2] SQL error: %s\n", zErrMsg); - sqlite3_free(zErrMsg); - } + char *sql2 = + "CREATE TABLE IF NOT EXISTS Finished(" + "jobid INT PRIMARY KEY NOT NULL," + "command TEXT NOT NULL," + "state INT NOT NULL," + "output_filename TEXT NOT NULL," + "store_output INT NOT NULL," + "pid INT NOT NULL," + "ts_UID INT NOT NULL," + "should_keep_finished INT NOT NULL," + "depend_on INT NOT NULL," + "depend_on_size INT NOT NULL," + "notify_errorlevel_to INT NOT NULL," + "notify_errorlevel_to_size INT NOT NULL," + "dependency_errorlevel INT NOT NULL," + "label TEXT NOT NULL," + "email TEXT NOT NULL," + "num_slots INT NOT NULL, " + "errorlevel INT NOT NULL, died_by_signal INT NOT NULL, signal INT NOT " + "NULL, " + "user_ms FLOAT NOT NULL, system_ms FLOAT NOT NULL, real_ms FLOAT NOT " + "NULL, skipped INT NOT NULL, " + "ptr TEXT NOT NULL, nchars INT NOT NULL, allocchars INT NOT NULL, " + "enqueue_time INT NOT NULL, start_time INT NOT NULL, end_time INT NOT " + "NULL, " + "enqueue_time_ms INT NOT NULL, start_time_ms INT NOT NULL, end_time_ms " + "INT NOT NULL, " + "order_id INT NOT NULL, command_strip INT NOT NULL, work_dir TEXT NOT " + "NULL);"; + + rc = sqlite3_exec(db, sql2, 0, 0, &zErrMsg); + + if (rc != SQLITE_OK) { + printf("[open_sqlite1] SQL error: %s\n", zErrMsg); + sqlite3_free(zErrMsg); + } else { + printf("Table Finished created successfully\n"); + } - return 0; + sql = + "CREATE TABLE IF NOT EXISTS Global(" + "id INT PRIMARY KEY NOT NULL," + "JOBIDs INT NOT NULL); INSERT INTO Global (id, JOBIDs) VALUES (1, 1000);"; + rc = sqlite3_exec(db, sql, 0, 0, &zErrMsg); + if (rc != SQLITE_OK) { + printf("[open_sqlite2] SQL error: %s\n", zErrMsg); + sqlite3_free(zErrMsg); + } + + return 0; } int get_jobids_DB() { - char *err_msg = 0; - char *sql = "SELECT JOBIDs FROM Global WHERE id=1;"; - int value = 0; - int rc = sqlite3_exec(db, sql, callback, &value, &err_msg); - if (rc != SQLITE_OK ) { - fprintf(stderr, "[get_jobids_DB] SQL error: %s\n", err_msg); - sqlite3_free(err_msg); - return 1000; - } - return value; + char *err_msg = 0; + char *sql = "SELECT JOBIDs FROM Global WHERE id=1;"; + int value = 0; + int rc = sqlite3_exec(db, sql, callback, &value, &err_msg); + if (rc != SQLITE_OK) { + fprintf(stderr, "[get_jobids_DB] SQL error: %s\n", err_msg); + sqlite3_free(err_msg); + return 1000; + } + return value; } void set_jobids_DB(int value) { - char *err_msg = 0; - char sql[1024]; - sprintf(sql, "INSERT OR REPLACE INTO Global (id, JOBIDs) VALUES (1, %d);", value); - int rc = sqlite3_exec(db, sql, 0, 0, &err_msg); - if (rc != SQLITE_OK ) { - fprintf(stderr, "[set_jobids_DB] SQL error: %s\n", err_msg); - sqlite3_free(err_msg); - } + char *err_msg = 0; + char sql[1024]; + sprintf(sql, "INSERT OR REPLACE INTO Global (id, JOBIDs) VALUES (1, %d);", + value); + int rc = sqlite3_exec(db, sql, 0, 0, &err_msg); + if (rc != SQLITE_OK) { + fprintf(stderr, "[set_jobids_DB] SQL error: %s\n", err_msg); + sqlite3_free(err_msg); + } } -int delete_DB(int jobid , const char* table) { - char sql[1024]; - sprintf(sql,"DELETE FROM %s WHERE jobid=%d;", table, jobid); - char* errmsg=NULL; +int delete_DB(int jobid, const char *table) { + char sql[1024]; + sprintf(sql, "DELETE FROM %s WHERE jobid=%d;", table, jobid); + char *errmsg = NULL; - if(sqlite3_exec(db ,sql,NULL,NULL,&errmsg)!=SQLITE_OK){ - fprintf(stderr,"[delete_DB] SQL error: %s by %s\n",errmsg, sql); - sqlite3_free(errmsg); - return -1;//返回-1表示删除失败 - } - return 0;//返回0表示删除成功 + if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK) { + fprintf(stderr, "[delete_DB] SQL error: %s by %s\n", errmsg, sql); + sqlite3_free(errmsg); + return -1; //返回-1表示删除失败 + } + return 0; //返回0表示删除成功 } +static int edit_DB(struct Job *job, const char *table, const char *action) { + struct Result *result = &(job->result); + struct Procinfo *info = &(job->info); + const char *label = job->label == NULL ? "(..)" : job->label; + const char *email = job->email == NULL ? "(..)" : job->email; -static int edit_DB(struct Job* job, const char* table, const char* action) { - struct Result* result = &(job->result); - struct Procinfo* info= &(job->info); - const char* label = job->label == NULL ? "(..)" : job->label; - const char* email = job->email == NULL ? "(..)" : job->email; + char sql[1024]; - char sql[1024]; - - int order_id = get_order_id(job->jobid); - if (order_id == 0) { - order_id = max_order_id() + 1; - } - char* depend_on = ints_to_chars(job->depend_on_size, job->depend_on, ","); - char* notify_errorlevel_to = ints_to_chars(job->notify_errorlevel_to_size, job->notify_errorlevel_to, ","); - - sprintf(sql, "%s INTO %s (jobid, command, state, output_filename, store_output, pid, ts_UID, should_keep_finished, depend_on, depend_on_size," \ - "notify_errorlevel_to, notify_errorlevel_to_size, dependency_errorlevel,label,email,num_slots,errorlevel,died_by_signal," \ - "signal,user_ms,system_ms,real_ms,skipped," \ - "ptr,nchars,allocchars," \ - "enqueue_time,start_time,end_time," \ - "enqueue_time_ms,start_time_ms,end_time_ms, " \ - "order_id, command_strip, work_dir)" \ - "VALUES (%d,'%s',%d,'%s',%d,%d,%d,%d,'%s',%d,'%s',%d,%d,'%s','%s',%d," \ - "%d,%d,%d,%f,%f,%f,%d,"\ - "'%s',%d,%d,'%ld','%ld','%ld','%ld','%ld','%ld', " \ - "%d, %d,'%s');", - action, - table, - job->jobid, - job->command, - job->state, - job->output_filename, - job->store_output, - job->pid, - job->ts_UID, - job->should_keep_finished, - depend_on, // job->depend_on, - job->depend_on_size, - notify_errorlevel_to, - job->notify_errorlevel_to_size, - job->dependency_errorlevel, - label, - email, - job->num_slots, - result->errorlevel, result->died_by_signal, result->signal, - result->user_ms, result->system_ms, result->real_ms, result->skipped, - info->ptr, info->nchars, info->allocchars, - info->enqueue_time.tv_sec, info->start_time.tv_sec, info->end_time.tv_sec, - info->enqueue_time.tv_usec, info->start_time.tv_usec, info->end_time.tv_usec, - order_id, job->command_strip, job->work_dir - ); - char *errmsg = NULL; - int rs = sqlite3_exec(db, sql,NULL,NULL,&errmsg); - free(depend_on); - free(notify_errorlevel_to); - if (rs != SQLITE_OK) { - fprintf(stderr,"[insert_DB] SQL error: %s by %s\n", errmsg, sql); - sqlite3_free(errmsg); - return -1; // 返回-1表示插入失败 - } - return 0; // 返回0表示插入成功 + int order_id = get_order_id(job->jobid); + if (order_id == 0) { + order_id = max_order_id() + 1; + } + char *depend_on = ints_to_chars(job->depend_on_size, job->depend_on, ","); + char *notify_errorlevel_to = ints_to_chars(job->notify_errorlevel_to_size, + job->notify_errorlevel_to, ","); + + sprintf( + sql, + "%s INTO %s (jobid, command, state, output_filename, store_output, pid, " + "ts_UID, should_keep_finished, depend_on, depend_on_size," + "notify_errorlevel_to, notify_errorlevel_to_size, " + "dependency_errorlevel,label,email,num_slots,errorlevel,died_by_signal," + "signal,user_ms,system_ms,real_ms,skipped," + "ptr,nchars,allocchars," + "enqueue_time,start_time,end_time," + "enqueue_time_ms,start_time_ms,end_time_ms, " + "order_id, command_strip, work_dir)" + "VALUES (%d,'%s',%d,'%s',%d,%d,%d,%d,'%s',%d,'%s',%d,%d,'%s','%s',%d," + "%d,%d,%d,%f,%f,%f,%d," + "'%s',%d,%d,'%ld','%ld','%ld','%ld','%ld','%ld', " + "%d, %d,'%s');", + action, table, job->jobid, job->command, job->state, job->output_filename, + job->store_output, job->pid, job->ts_UID, job->should_keep_finished, + depend_on, // job->depend_on, + job->depend_on_size, notify_errorlevel_to, job->notify_errorlevel_to_size, + job->dependency_errorlevel, label, email, job->num_slots, + result->errorlevel, result->died_by_signal, result->signal, + result->user_ms, result->system_ms, result->real_ms, result->skipped, + info->ptr, info->nchars, info->allocchars, info->enqueue_time.tv_sec, + info->start_time.tv_sec, info->end_time.tv_sec, + info->enqueue_time.tv_usec, info->start_time.tv_usec, + info->end_time.tv_usec, order_id, job->command_strip, job->work_dir); + char *errmsg = NULL; + int rs = sqlite3_exec(db, sql, NULL, NULL, &errmsg); + free(depend_on); + free(notify_errorlevel_to); + if (rs != SQLITE_OK) { + fprintf(stderr, "[insert_DB] SQL error: %s by %s\n", errmsg, sql); + sqlite3_free(errmsg); + return -1; // 返回-1表示插入失败 + } + return 0; // 返回0表示插入成功 } - -int insert_DB(struct Job* job, const char* table) { - return edit_DB(job, table, "INSERT"); +int insert_DB(struct Job *job, const char *table) { + return edit_DB(job, table, "INSERT"); } -int insert_or_replace_DB(struct Job* job, const char* table) { - return edit_DB(job, table, "INSERT OR REPLACE"); +int insert_or_replace_DB(struct Job *job, const char *table) { + return edit_DB(job, table, "INSERT OR REPLACE"); } static void set_order_id_DB(int jobid, int order_id) { - char *err_msg = 0; - char sql[1024]; - sprintf(sql, "UPDATE Jobs SET order_id=%d WHERE jobid=%d", order_id, jobid); - int rc = sqlite3_exec(db, sql, 0, 0, &err_msg); - if (rc != SQLITE_OK ) { - fprintf(stderr, "[movetop_DB] SQL error: %s\n", err_msg); - sqlite3_free(err_msg); - } + char *err_msg = 0; + char sql[1024]; + sprintf(sql, "UPDATE Jobs SET order_id=%d WHERE jobid=%d", order_id, jobid); + int rc = sqlite3_exec(db, sql, 0, 0, &err_msg); + if (rc != SQLITE_OK) { + fprintf(stderr, "[movetop_DB] SQL error: %s\n", err_msg); + sqlite3_free(err_msg); + } } void set_state_DB(int jobid, int state) { - char *err_msg = 0; - char sql[1024]; - sprintf(sql, "UPDATE Jobs SET state=%d WHERE jobid=%d", state, jobid); - int rc = sqlite3_exec(db, sql, 0, 0, &err_msg); - if (rc != SQLITE_OK ) { - fprintf(stderr, "[movetop_DB] SQL error: %s\n", err_msg); - sqlite3_free(err_msg); - } + char *err_msg = 0; + char sql[1024]; + sprintf(sql, "UPDATE Jobs SET state=%d WHERE jobid=%d", state, jobid); + int rc = sqlite3_exec(db, sql, 0, 0, &err_msg); + if (rc != SQLITE_OK) { + fprintf(stderr, "[movetop_DB] SQL error: %s\n", err_msg); + sqlite3_free(err_msg); + } } void swap_DB(int jobid0, int jobid1) { - int id0 = get_order_id(jobid0); - int id1 = get_order_id(jobid1); - set_order_id_DB(jobid0, id1); - set_order_id_DB(jobid1, id0); + int id0 = get_order_id(jobid0); + int id1 = get_order_id(jobid1); + set_order_id_DB(jobid0, id1); + set_order_id_DB(jobid1, id0); } void movetop_DB(int jobid) { - int order_id = min_order_id() - 1; - if (order_id == 0) order_id = -1; - set_order_id_DB(jobid, order_id); + int order_id = min_order_id() - 1; + if (order_id == 0) + order_id = -1; + set_order_id_DB(jobid, order_id); } /* @@ -317,165 +323,155 @@ static void clear_DB(const char* table) { } } */ -int read_jobid_DB(int** jobids, const char* table) { - int n; - char sql[1024]; - sprintf(sql, "SELECT COUNT(*) FROM %s;", table); - char *errmsg = NULL; - - if (sqlite3_exec(db, sql,NULL,NULL,&errmsg) != SQLITE_OK) { - fprintf(stderr,"[read_jobid_DB0] SQL error: %s by %s\n", - sqlite3_errmsg(db), sql); - sqlite3_free(errmsg); - return -1; // 返回-1表示查询失败 - } +int read_jobid_DB(int **jobids, const char *table) { + int n; + char sql[1024]; + sprintf(sql, "SELECT COUNT(*) FROM %s;", table); + char *errmsg = NULL; + + if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK) { + fprintf(stderr, "[read_jobid_DB0] SQL error: %s by %s\n", + sqlite3_errmsg(db), sql); + sqlite3_free(errmsg); + return -1; // 返回-1表示查询失败 + } - // 从查询结果中读取数据 - sqlite3_stmt *stmt; - int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc != SQLITE_OK) { - fprintf(stderr,"[read_jobid_DB1] SQL error: %s by %s\n", - sqlite3_errmsg(db), sql); - return -2; // 返回-1表示查询失败 - } + // 从查询结果中读取数据 + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + fprintf(stderr, "[read_jobid_DB1] SQL error: %s by %s\n", + sqlite3_errmsg(db), sql); + return -2; // 返回-1表示查询失败 + } - if (sqlite3_step(stmt) == SQLITE_ROW) { - n = sqlite3_column_int(stmt, 0); - } - if (n == 0) return 0; - *jobids = (int*)malloc(n * sizeof(int)); - - sprintf(sql, "SELECT jobid FROM %s ORDER BY order_id;", table); - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc != SQLITE_OK) { - fprintf(stderr,"[read_jobid_DB2] SQL error: %s from %s\n", - sqlite3_errmsg(db), sql); - debug_write("test0"); - return -3; // 返回-1表示查询失败 - } + if (sqlite3_step(stmt) == SQLITE_ROW) { + n = sqlite3_column_int(stmt, 0); + } + if (n == 0) + return 0; + *jobids = (int *)malloc(n * sizeof(int)); + + sprintf(sql, "SELECT jobid FROM %s ORDER BY order_id;", table); + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + fprintf(stderr, "[read_jobid_DB2] SQL error: %s from %s\n", + sqlite3_errmsg(db), sql); + debug_write("test0"); + return -3; // 返回-1表示查询失败 + } - int i = 0; - while (sqlite3_step(stmt) == SQLITE_ROW) { - (*jobids)[i++] = sqlite3_column_int(stmt, 0); - } + int i = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) { + (*jobids)[i++] = sqlite3_column_int(stmt, 0); + } - return n; // 返回0表示查询成功 + return n; // 返回0表示查询成功 } +struct Job *read_DB(int jobid, const char *table) { + struct Job *job = (struct Job *)malloc(sizeof(struct Job) * 1); +#ifdef TASKSET + job->cores = NULL; +#endif + struct Result *result = &(job->result); + struct Procinfo *info = &(job->info); + + char sql[2048]; + sprintf(sql, "SELECT * FROM %s WHERE jobid=%d;", table, jobid); + char *errmsg = NULL; + + if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK) { + fprintf(stderr, "[read_DB0] SQL error: %s\n", errmsg); + sqlite3_free(errmsg); + return NULL; // 返回-1表示查询失败 + } -struct Job* read_DB(int jobid, const char* table) { - struct Job* job = (struct Job*)malloc(sizeof(struct Job)*1); - #ifdef TASKSET - job->cores = NULL; - #endif - struct Result* result = &(job->result); - struct Procinfo* info= &(job->info); + // 从查询结果中读取数据 + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + fprintf(stderr, "[read_DB1] SQL error: %s\n", sqlite3_errmsg(db)); + return NULL; // 返回-1表示查询失败 + } - char sql[2048]; - sprintf(sql, "SELECT * FROM %s WHERE jobid=%d;", table, jobid); - char *errmsg = NULL; + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + // 从查询结果中读取数据 + job->jobid = sqlite3_column_int(stmt, 0); - if (sqlite3_exec(db, sql,NULL,NULL,&errmsg) != SQLITE_OK) { - fprintf(stderr,"[read_DB0] SQL error: %s\n", errmsg); - sqlite3_free(errmsg); - return NULL; // 返回-1表示查询失败 - } + strcpy(sql, (const char *)sqlite3_column_text(stmt, 1)); + copy_with_nullcheck(&(job->command), sql); - // 从查询结果中读取数据 - sqlite3_stmt *stmt; - int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc != SQLITE_OK) { - fprintf(stderr,"[read_DB1] SQL error: %s\n", sqlite3_errmsg(db)); - return NULL; // 返回-1表示查询失败 - } + job->state = sqlite3_column_int(stmt, 2); - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - // 从查询结果中读取数据 - job->jobid = sqlite3_column_int(stmt, 0); - - strcpy(sql, (const char*) sqlite3_column_text(stmt, 1)); - job->command = (char*) malloc(sizeof(char) * (strlen(sql)+1)); - strcpy(job->command, sql); - - job->state = sqlite3_column_int(stmt, 2); - - strcpy(sql, (const char*) sqlite3_column_text(stmt, 3)); - job->output_filename = (char*) malloc(sizeof(char) * (strlen(sql)+1)); - strcpy(job->output_filename, sql); - - job->store_output = sqlite3_column_int(stmt, 4); - job->pid = sqlite3_column_int(stmt, 5); - job->ts_UID = sqlite3_column_int(stmt, 6); - job->should_keep_finished = sqlite3_column_int(stmt, 7); - - - // job->depend_on_size = sqlite3_column_bytes(stmt, 9); - // job->notify_errorlevel_to_size = sqlite3_column_bytes(stmt, 11); - job->depend_on_size = sqlite3_column_int(stmt, 9); - if (job->depend_on_size == 0) { - job->depend_on = NULL; - } else { - strcpy(sql, (const char*) sqlite3_column_text(stmt, 8)); - job->depend_on = chars_to_ints(&job->depend_on_size, sql, ","); - } - - job->notify_errorlevel_to_size = sqlite3_column_int(stmt, 11); - if (job->notify_errorlevel_to_size == 0) { - job->notify_errorlevel_to = NULL; - } else { - strcpy(sql, (const char*) sqlite3_column_text(stmt, 10)); - job->notify_errorlevel_to = chars_to_ints(&job->notify_errorlevel_to_size, sql, ","); - } - - job->dependency_errorlevel = sqlite3_column_int(stmt, 12); - - strcpy(sql, (const char*) sqlite3_column_text(stmt, 13)); - job->label = (char*) malloc(sizeof(char) * (strlen(sql)+1)); - strcpy(job->label, sql); - - strcpy(sql, (const char*) sqlite3_column_text(stmt, 14)); - job->email = (char*) malloc(sizeof(char) * (strlen(sql)+1)); - if (strcmp(sql, "(..)") != 0) { - strcpy(job->email, sql); - } else { - job->email = NULL; - } - - job->num_slots = sqlite3_column_int(stmt, 15); - - result->errorlevel = sqlite3_column_int(stmt, 16); - result->died_by_signal = sqlite3_column_int(stmt, 17); - result->signal = sqlite3_column_int(stmt, 18); - result->user_ms = (float)sqlite3_column_double(stmt, 19); - result->system_ms = (float)sqlite3_column_double(stmt, 20); - result->real_ms = (float)sqlite3_column_double(stmt, 21); - result->skipped = sqlite3_column_int(stmt, 22); - - strcpy(sql, (const char*) sqlite3_column_text(stmt, 23)); - info->ptr = (char*) malloc(sizeof(char) * (strlen(sql)+1)); - strcpy(info->ptr, sql); - - info->nchars=sqlite3_column_bytes(stmt,24)/sizeof(char); - info->allocchars=sqlite3_column_bytes(stmt,25)/sizeof(char); - - info->enqueue_time.tv_sec=sqlite3_column_int64(stmt,26); - info->start_time.tv_sec=sqlite3_column_int64(stmt,27); - info->end_time.tv_sec=sqlite3_column_int64(stmt,28); - - info->enqueue_time.tv_usec=sqlite3_column_int64(stmt,29); - info->start_time.tv_usec=sqlite3_column_int64(stmt,30); - info->end_time.tv_usec=sqlite3_column_int64(stmt,31); - job->command_strip=sqlite3_column_int(stmt, 33); - - strcpy(sql, (const char*) sqlite3_column_text(stmt, 34)); - job->work_dir = (char*) malloc(sizeof(char) * (strlen(sql)+1)); - strcpy(job->work_dir, sql); + strcpy(sql, (const char *)sqlite3_column_text(stmt, 3)); + copy_with_nullcheck(&(job->output_filename), sql); + job->store_output = sqlite3_column_int(stmt, 4); + job->pid = sqlite3_column_int(stmt, 5); + job->ts_UID = sqlite3_column_int(stmt, 6); + job->should_keep_finished = sqlite3_column_int(stmt, 7); + + // job->depend_on_size = sqlite3_column_bytes(stmt, 9); + // job->notify_errorlevel_to_size = sqlite3_column_bytes(stmt, 11); + job->depend_on_size = sqlite3_column_int(stmt, 9); + if (job->depend_on_size == 0) { + job->depend_on = NULL; } else { - fprintf(stderr,"[read_DB2] SQL error: %s\n", sqlite3_errmsg(db)); - return NULL; // 返回-1表示查询失败 + strcpy(sql, (const char *)sqlite3_column_text(stmt, 8)); + job->depend_on = chars_to_ints(&job->depend_on_size, sql, ","); } - - return job; // 返回0表示查询成功 + + job->notify_errorlevel_to_size = sqlite3_column_int(stmt, 11); + if (job->notify_errorlevel_to_size == 0) { + job->notify_errorlevel_to = NULL; + } else { + strcpy(sql, (const char *)sqlite3_column_text(stmt, 10)); + job->notify_errorlevel_to = + chars_to_ints(&job->notify_errorlevel_to_size, sql, ","); + } + + job->dependency_errorlevel = sqlite3_column_int(stmt, 12); + + strcpy(sql, (const char *)sqlite3_column_text(stmt, 13)); + copy_with_nullcheck(&(job->label), sql); + + strcpy(sql, (const char *)sqlite3_column_text(stmt, 14)); + copy_with_nullcheck(&(job->email), sql); + + job->num_slots = sqlite3_column_int(stmt, 15); + + result->errorlevel = sqlite3_column_int(stmt, 16); + result->died_by_signal = sqlite3_column_int(stmt, 17); + result->signal = sqlite3_column_int(stmt, 18); + result->user_ms = (float)sqlite3_column_double(stmt, 19); + result->system_ms = (float)sqlite3_column_double(stmt, 20); + result->real_ms = (float)sqlite3_column_double(stmt, 21); + result->skipped = sqlite3_column_int(stmt, 22); + + strcpy(sql, (const char *)sqlite3_column_text(stmt, 23)); + copy_with_nullcheck(&(info->ptr), sql); + + info->nchars = sqlite3_column_bytes(stmt, 24) / sizeof(char); + info->allocchars = sqlite3_column_bytes(stmt, 25) / sizeof(char); + + info->enqueue_time.tv_sec = sqlite3_column_int64(stmt, 26); + info->start_time.tv_sec = sqlite3_column_int64(stmt, 27); + info->end_time.tv_sec = sqlite3_column_int64(stmt, 28); + + info->enqueue_time.tv_usec = sqlite3_column_int64(stmt, 29); + info->start_time.tv_usec = sqlite3_column_int64(stmt, 30); + info->end_time.tv_usec = sqlite3_column_int64(stmt, 31); + job->command_strip = sqlite3_column_int(stmt, 33); + + strcpy(sql, (const char *)sqlite3_column_text(stmt, 34)); + copy_with_nullcheck(&(job->work_dir), sql); + + } else { + fprintf(stderr, "[read_DB2] SQL error: %s\n", sqlite3_errmsg(db)); + return NULL; // 返回-1表示查询失败 + } + + return job; // 返回0表示查询成功 } From 0f873d6954246055d32b90b314641109b82f0f0e Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Sat, 28 Oct 2023 11:49:38 +0800 Subject: [PATCH 80/91] add duplicate check in the insert of arguments --- jobs.c | 7 +-- list.c | 12 ++--- main.h | 2 +- print.c | 137 +++++++++++++++++++++++++++++++------------------------- 4 files changed, 87 insertions(+), 71 deletions(-) diff --git a/jobs.c b/jobs.c index 2e32493..4e5955f 100644 --- a/jobs.c +++ b/jobs.c @@ -1475,7 +1475,7 @@ void job_finished(const struct Result *result, int jobid) { jpointer->next = newfirst; } } -static int fork_cmd(int UID, const char *path, const char *cmd) { +static int fork_cmd(const int UID, const char *path, const char *cmd) { int pid = -1; //定义一个进程ID变量 pid = fork(); //调用fork()函数创建子进程 @@ -1526,9 +1526,10 @@ static void s_add_job(struct Job *j, struct Job **p) { char c[64]; sprintf(c, " --relink %d -J %d ", j->pid, j->jobid); - char *str = insert_chars(j->command_strip, j->command, c); + char *str = insert_chars_check(j->command_strip, j->command, c); fork_cmd(user_UID[j->ts_UID], j->work_dir, str); + free(str); // fork_cmd(0, j->work_dir, str); jobids = jobids > j->jobid ? jobids : j->jobid + 1; @@ -1548,7 +1549,7 @@ static void s_add_job(struct Job *j, struct Job **p) { char c[32]; sprintf(c, " -J %d ", j->jobid); - char *str = insert_chars(j->command_strip, j->command, c); + char *str = insert_chars_check(j->command_strip, j->command, c); fork_cmd(user_UID[j->ts_UID], j->work_dir, str); jobids = jobids > j->jobid ? jobids : j->jobid + 1; diff --git a/list.c b/list.c index 1c3a1f6..bd9e31c 100644 --- a/list.c +++ b/list.c @@ -85,8 +85,8 @@ char *joblist_headers() { line = malloc(256); snprintf(line, 256, - "%-4s %-7s %-7s %-6s %-10s %6s %-20s %s [run=%i/%i %.2f%%] Used Proc: %-3d %s\n", - "ID", "State", "Proc.", "User", "Label", "Time", "Command", "Log", + "%-4s %-9s %-6s %-7s %-10s %7s %-20s Log [run=%i/%i %.2f%%] Used Proc: %-3d %s\n", + "ID", "State", "Proc.", "User", "Label", "Time", "Command", busy_slots, max_slots, 100.0 * busy_slots / max_slots, core_usage, extra); return line; } @@ -193,7 +193,7 @@ static char *print_noresult(const struct Job *p) { char *cmd = shorten(p->command + p->command_strip, cmd_len); if (p->label) { char *label = shorten(p->label, 10); - snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-21s | %s\n", + snprintf(line, maxlen, "%-4i %-9s %-6i %-7s %-10s %6.2f%s %-21s | %s\n", p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, output_filename); free(label); @@ -201,7 +201,7 @@ static char *print_noresult(const struct Job *p) { } else { char *cmd = shorten(p->command + p->command_strip, cmd_len); char *label = "(..)"; - snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-21s | %s\n", + snprintf(line, maxlen, "%-4i %-9s %-6i %-7s %-10s %6.2f%s %-21s | %s\n", p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, output_filename); free(cmd); @@ -261,7 +261,7 @@ static char *print_result(const struct Job *p) { char *cmd = shorten(p->command + p->command_strip, cmd_len); if (p->label) { char *label = shorten(p->label, 10); - snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-21s | %s\n", + snprintf(line, maxlen, "%-4i %-9s %-6i %-7s %-10s %6.2f%s %-21s | %s\n", p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, output_filename); free(label); @@ -269,7 +269,7 @@ static char *print_result(const struct Job *p) { } else { char *cmd = shorten(p->command + p->command_strip, cmd_len); char *label = "(..)"; - snprintf(line, maxlen, "%-4i %-10s %-3i %-7s %-10s %5.2f%s %-21s | %s\n", + snprintf(line, maxlen, "%-4i %-9s %-6i %-7s %-10s %6.2f%s %-21s | %s\n", p->jobid, jobstate, p->num_slots, uname, label, real_ms, unit, cmd, output_filename); free(cmd); diff --git a/main.h b/main.h index 07cf5e8..086923a 100644 --- a/main.h +++ b/main.h @@ -615,7 +615,7 @@ void set_state_DB(int jobid, int state); /* print.c */ char* ints_to_chars(int n, int *array, const char *delim); int* chars_to_ints(int *size, char* str, const char* delim); -char* insert_chars(int pos, const char* input, const char* c); +char* insert_chars_check(int pos, const char* input, const char* c); /* taskset.c */ void init_taskset(); diff --git a/print.c b/print.c index 6505b63..4336467 100644 --- a/print.c +++ b/print.c @@ -4,94 +4,109 @@ Please find the license in the provided COPYING file. */ +#include "main.h" +#include +#include #include #include -#include -#include -#include #include -#include "main.h" - +#include /* maxsize: max buffer size, with '\0' */ int fd_nprintf(int fd, int maxsize, const char *fmt, ...) { - va_list ap; - char *out; - int size; - int rest; + va_list ap; + char *out; + int size; + int rest; - va_start(ap, fmt); + va_start(ap, fmt); - out = (char *) malloc(maxsize * sizeof(char)); - if (out == 0) { - warning("Not enough memory in fd_nprintf()"); - return -1; - } + out = (char *)malloc(maxsize * sizeof(char)); + if (out == 0) { + warning("Not enough memory in fd_nprintf()"); + return -1; + } - size = vsnprintf(out, maxsize, fmt, ap); + size = vsnprintf(out, maxsize, fmt, ap); - rest = size; /* We don't want the last null character */ - while (rest > 0) { - int res; - res = write(fd, out, rest); - if (res == -1) { - warning("Cannot write more chars in pinfo_dump"); - break; - } - rest -= res; + rest = size; /* We don't want the last null character */ + while (rest > 0) { + int res; + res = write(fd, out, rest); + if (res == -1) { + warning("Cannot write more chars in pinfo_dump"); + break; } + rest -= res; + } - free(out); + free(out); - return size; + return size; } - char *ints_to_chars(int n, int *array, const char *delim) { - int size = n * 12 + n * strlen(delim) + 1; - char *tmp = (char*) malloc(size * sizeof(char)); - tmp[0] = '\0'; - int j = 0; - for (int i = 0; i < n; i++) { - j += sprintf(tmp + j, "%d", array[i]); - if (i < n - 1) { - strcat(tmp, delim); - j += strlen(delim); - } + int size = n * 12 + n * strlen(delim) + 1; + char *tmp = (char *)malloc(size * sizeof(char)); + tmp[0] = '\0'; + int j = 0; + for (int i = 0; i < n; i++) { + j += sprintf(tmp + j, "%d", array[i]); + if (i < n - 1) { + strcat(tmp, delim); + j += strlen(delim); } - return tmp; + } + return tmp; } -int* chars_to_ints(int *size, char* str, const char* delim) { - int count = 0; - for (int i = 0; str[i]; i++) { - if (str[i] == delim[0]) { - count++; - } - } - count++; - int *result = malloc(count * sizeof(int)); - result[0] = '\0'; - char *token = strtok(str, delim); - int index = 0; - while (token != NULL) { - result[index++] = atoi(token); - token = strtok(NULL, delim); +int *chars_to_ints(int *size, char *str, const char *delim) { + int count = 0; + for (int i = 0; str[i]; i++) { + if (str[i] == delim[0]) { + count++; } - *size = count; - return result; + } + count++; + int *result = malloc(count * sizeof(int)); + result[0] = '\0'; + char *token = strtok(str, delim); + int index = 0; + while (token != NULL) { + result[index++] = atoi(token); + token = strtok(NULL, delim); + } + *size = count; + return result; } -char* insert_chars(int pos, const char* input, const char* c) { - int len = strlen(input) + strlen(c) + 1; - char *str = (char *)calloc(len, sizeof(char)); //用malloc函数分配内存 +char *insert_chars_check(int pos, const char *input, const char *s_arg) { + const int arg_len = strlen(s_arg); + const int input_len = strlen(input) + 1; + const int pos_head = pos - arg_len; + char *str = NULL; + + // printf("head = [%s],[%s]\n", input + pos_head, s_arg, )); + if (strncmp(input + pos_head, s_arg, arg_len) == 0) { + str = (char *)malloc((input_len + 1) * sizeof(char)); if (str == NULL) //判断是否分配成功 { error("Memory allocation failed.\n"); return NULL; } + strcpy(str, input); //将s数组的前t个字符复制到str中 + return str; + } else { + int len = input_len + arg_len + 1; + str = (char *)calloc(len, sizeof(char)); //用malloc函数分配内存 + if (str == NULL) //判断是否分配成功 + { + error("Memory allocation failed.\n"); + return NULL; + } strncpy(str, input, pos); //将s数组的前t个字符复制到str中 - strcat(str, c); //将数字字符串连接到str后面 + strcat(str, s_arg); //将数字字符串连接到str后面 strcat(str, input + pos); //将s剩余的字符串连接到str后面 - return str; + } + return str; } \ No newline at end of file From c65ece333a3dbf13272a188044419a7aa384f16c Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Sat, 28 Oct 2023 13:45:00 +0800 Subject: [PATCH 81/91] add --no-bind option or remove the mpi binding --- Makefile | 2 +- client.c | 3 ++- jobs.c | 7 ++++++- list.c | 2 +- main.c | 12 ++++++++++-- main.h | 3 +++ taskset.c | 4 +++- user.c | 2 +- 8 files changed, 27 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index d1bd839..c3afe03 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ PREFIX?=/usr/local PREFIX_LOCAL=~ GLIBCFLAGS=#-D_XOPEN_SOURCE=500 -D__STRICT_ANSI__ CPPFLAGS+=$(GLIBCFLAGS) -CFLAGS?=-pedantic -ansi -Wall -g -std=gnu11 -DTASKSET -DSOUND -fcommon -Wno-format-truncation +CFLAGS?=-pedantic -ansi -Wall -g -std=gnu11 -DTASKSET_ -DSOUND -fcommon -Wno-format-truncation OBJECTS=main.o \ server.o \ server_start.o \ diff --git a/client.c b/client.c index dd6d69f..f193c87 100644 --- a/client.c +++ b/client.c @@ -90,6 +90,7 @@ void c_new_job() { } else m.u.newjob.email_size = 0; + m.u.newjob.store_output = command_line.store_output; m.u.newjob.depend_on_size = command_line.depend_on_size; m.u.newjob.should_keep_finished = command_line.should_keep_finished; @@ -98,7 +99,7 @@ void c_new_job() { m.u.newjob.num_slots = command_line.num_slots; m.u.newjob.taskpid = command_line.taskpid; m.u.newjob.start_time = command_line.start_time; - + m.u.newjob.taskset_flag = command_line.taskset_flag; diff --git a/jobs.c b/jobs.c index 4e5955f..d294cf3 100644 --- a/jobs.c +++ b/jobs.c @@ -923,7 +923,11 @@ static struct Job *newjobptr() { p = p->next; p->next = (struct Job *)calloc(sizeof(struct Job), sizeof(char)); - +#ifdef TASKSET + p->next->taskset_flag = 1; +#else + p->next->taskset_flag = 0; +#endif /* p->next->next = 0; p->next->output_filename = 0; @@ -1037,6 +1041,7 @@ int s_newjob(int s, struct Msg *m, int ts_UID) { p->notify_errorlevel_to_size = 0; p->depend_on_size = m->u.newjob.depend_on_size; p->depend_on = 0; + p->taskset_flag = m->u.newjob.taskset_flag; /* this error level here is used internally to decide whether a job should be * run or not so it only matters whether the error level is 0 or not. thus, diff --git a/list.c b/list.c index bd9e31c..eafd8ea 100644 --- a/list.c +++ b/list.c @@ -85,7 +85,7 @@ char *joblist_headers() { line = malloc(256); snprintf(line, 256, - "%-4s %-9s %-6s %-7s %-10s %7s %-20s Log [run=%i/%i %.2f%%] Used Proc: %-3d %s\n", + "%-4s %-9s %-6s %-7s %-10s %7s %-20s Log [run=%i/%i %.2f%%] Bind Cores: %-3d %s\n", "ID", "State", "Proc.", "User", "Label", "Time", "Command", busy_slots, max_slots, 100.0 * busy_slots / max_slots, core_usage, extra); return line; diff --git a/main.c b/main.c index a4ce43b..21f6385 100644 --- a/main.c +++ b/main.c @@ -67,6 +67,11 @@ static void default_command_line() { command_line.start_time = 0; command_line.jobid = 0; command_line.list_format = DEFAULT; +#ifdef TASKSET + command_line.taskset_flag = 1; +#else + command_line.taskset_flag = 0; +#endif } struct Msg default_msg() { @@ -178,6 +183,7 @@ static struct option longOptions[] = { {"jobid", required_argument, NULL, 'J'}, {"stime", required_argument, NULL, 0}, {"check_daemon", no_argument, NULL, 0}, + {"no-bind", no_argument, NULL, 0}, {NULL, 0, NULL, 0}}; void parse_opts(int argc, char **argv) { @@ -255,6 +261,8 @@ void parse_opts(int argc, char **argv) { } } else if (strcmp(longOptions[optionIdx].name, "stime") == 0) { command_line.start_time = str2int(optarg); + } else if (strcmp(longOptions[optionIdx].name, "no-bind") == 0) { + command_line.taskset_flag = 0; } else error("Wrong option %s.", longOptions[optionIdx].name); break; @@ -648,7 +656,7 @@ static void print_help(const char *cmd) { printf(" --cont [jobid] Resume a paused task by its job " "ID.\n"); printf(" --suspend [user] For regular users, pause all tasks " - "and lock the user account. \n"); + "and lock the user account. \n"); printf(" For root user, lock all user " "accounts or a specific user's account.\n"); printf(" --resume [user] For regular users, resume all " @@ -660,7 +668,7 @@ static void print_help(const char *cmd) { printf(" --unlock Unlock the server.\n"); printf(" --relink [PID] Relink running tasks using their " "[PID] in case of an unexpected failure.\n"); - + printf(" --no-taskset turn off taskset\n"); printf(" --job [joibid] || -J [joibid] set the jobid of the new or relink " "job\n"); // printf(" --stime [start_time] Set the relinked task by starting diff --git a/main.h b/main.h index 086923a..1a79ea9 100644 --- a/main.h +++ b/main.h @@ -137,6 +137,7 @@ struct CommandLine { char *email; char *logfile; char *outfile; + int taskset_flag; int num_slots; /* Slots for the job to use. Default 1 */ int taskpid; /* to restore task by pid */ int require_elevel; /* whether requires error level of dependencies or not */ @@ -184,6 +185,7 @@ struct Msg { int num_slots; int taskpid; long start_time; + int taskset_flag; } newjob; struct { int ofilename_size; @@ -244,6 +246,7 @@ struct Job { int *notify_errorlevel_to; int notify_errorlevel_to_size; int dependency_errorlevel; + int taskset_flag; char *label; char *email; struct Procinfo info; diff --git a/taskset.c b/taskset.c index c587edd..a936e6e 100644 --- a/taskset.c +++ b/taskset.c @@ -9,6 +9,7 @@ static int core_id[MAX_CORE_NUM]; // = {0,64,1,65,2,66,3,67,4,68,5,69,6,70,7,71,8,72,9,73,10,74,11,75,12,76,13,77,14,78,15,79,16,80,17,81,18,82,19,83,20,84,21,85,22,86,23,87,24,88,25,89,26,90,27,91,28,92,29,93,30,94,31,95,32,96,33,97,34,98,35,99,36,100,37,101,38,102,39,103,40,104,41,105,42,106,43,107,44,108,45,109,46,110,47,111,48,112,49,113,50,114,51,115,52,116,53,117,54,118,55,119,56,120,57,121,58,122,59,123,60,124,61,125,62,126,63,127,128,192,129,193,130,194,131,195,132,196,133,197,134,198,135,199,136,200,137,201,138,202,139,203,140,204,141,205,142,206,143,207,144,208,145,209,146,210,147,211,148,212,149,213,150,214,151,215,152,216,153,217,154,218,155,219,156,220,157,221,158,222,159,223,160,224,161,225,162,226,163,227,164,228,165,229,166,230,167,231,168,232,169,233,170,234,171,235,172,236,173,237,174,238,175,239,176,240,177,241,178,242,179,243,180,244,181,245,182,246,183,247,184,248,185,249,186,250,187,251,188,252,189,253,190,254,191,255}; + static struct Job* core_jobs[MAX_CORE_NUM] = { NULL }; int task_cores_id[MAX_CORE_NUM] = {0}; int task_array_id[MAX_CORE_NUM] = {0}; @@ -55,7 +56,7 @@ void lock_core_by_job(struct Job* p) { void unlock_core_by_job(struct Job* p) { #ifdef TASKSET - if (p == NULL) return; + if (p == NULL || p->taskset_flag == 0) return; for (int i = 0; i < MAX_CORE_NUM; i++) { if (core_jobs[i] == p) { core_jobs[i] = NULL; @@ -69,6 +70,7 @@ void unlock_core_by_job(struct Job* p) { int set_task_cores(struct Job* p, const char* extra) { if (p == NULL || p->pid <= 0) return -1; + if (p->taskset_flag == 0) return 0; #ifdef TASKSET int N = p->num_slots; diff --git a/user.c b/user.c index 8c23c6a..5bb2bc7 100644 --- a/user.c +++ b/user.c @@ -304,7 +304,7 @@ void kill_pid(int pid, const char *signal, const char* extra) { command = (char*) malloc(size + strlen(extra)); sprintf(command, "bash %s %d \"%s\" \"%s\"", path, pid, signal, extra); } - // printf("command = %s\n", command); + printf("command = %s\n", command); // fp = popen(command, "r"); // FILE* f = fopen("/home/kylin/task-spooler/file.log", "a"); //fprintf(f, "%s\n", command); From ea0b6f8a77528276ee61873e2a46c1e49c1c7d1b Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Fri, 26 Jan 2024 16:42:51 +0800 Subject: [PATCH 82/91] fixed some typo --- jobs.c | 5 ++--- main.c | 4 ++-- version.h | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/jobs.c b/jobs.c index d294cf3..aeb0eb2 100644 --- a/jobs.c +++ b/jobs.c @@ -1831,8 +1831,7 @@ void s_resume_user(int s, int ts_UID) { } p = p->next; } - - snprintf(buff, 255, "Resume user: [%d] %199s\n", user_UID[ts_UID], + snprintf(buff, 255, "Resume user: [%04d] %-20s\n", user_UID[ts_UID], user_name[ts_UID]); send_list_line(s, buff); } @@ -1870,7 +1869,7 @@ void s_suspend_user(int s, int ts_UID) { p = p->next; } - snprintf(buff, 255, "Lock user: [%d] %s\n", user_UID[ts_UID], + snprintf(buff, 255, "Suspend user: [%04d] %-20s\n", user_UID[ts_UID], user_name[ts_UID]); send_list_line(s, buff); } diff --git a/main.c b/main.c index 21f6385..734000c 100644 --- a/main.c +++ b/main.c @@ -657,11 +657,11 @@ static void print_help(const char *cmd) { "ID.\n"); printf(" --suspend [user] For regular users, pause all tasks " "and lock the user account. \n"); - printf(" For root user, lock all user " + printf(" For root user, lock all user " "accounts or a specific user's account.\n"); printf(" --resume [user] For regular users, resume all " "paused tasks and unlock the user account. \n"); - printf(" For root user, unlock all user " + printf(" For root user, unlock all user " "accounts or a specific user's account.\n"); printf(" --lock Lock the server (Timeout: 30 " "seconds). For root user, there is no timeout.\n"); diff --git a/version.h b/version.h index 6b987bb..c382ac6 100644 --- a/version.h +++ b/version.h @@ -6,7 +6,7 @@ #define TASK_SPOOLER_VERSION_H #ifndef TS_VERSION -#define TS_VERSION 2.1.0 +#define TS_VERSION 2.1.0a #endif /* from https://github.com/LLNL/lbann/issues/117 From 219dfe301f5e6399bd1206e611a1f90f9de9606b Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Fri, 23 Feb 2024 14:19:30 +0800 Subject: [PATCH 83/91] add failure check for ts and enlarge the buff size --- jobs.c | 6 ++-- main.h | 10 +++--- sqlite.c | 102 ++++++++++++++++++++++++++++++++++--------------------- 3 files changed, 72 insertions(+), 46 deletions(-) diff --git a/jobs.c b/jobs.c index aeb0eb2..cbef8e9 100644 --- a/jobs.c +++ b/jobs.c @@ -1374,8 +1374,10 @@ static void new_finished_job(struct Job *j) { p->next = j; p->next->next = 0; - insert_DB(j, "Finished"); - delete_DB(j->jobid, "Jobs"); + int err = insert_DB(j, "Finished"); + if (err == 0) { + delete_DB(j->jobid, "Jobs"); + } #ifdef TASKSET unlock_core_by_job(j); diff --git a/main.h b/main.h index 1a79ea9..fea0ecb 100644 --- a/main.h +++ b/main.h @@ -601,17 +601,17 @@ void c_check_daemon(); /* sqlite.c */ const char *get_sqlite_path(); int open_sqlite(); -void close_sqlite(); +int close_sqlite(); int insert_DB(struct Job* job, const char* table); int insert_or_replace_DB(struct Job* job, const char* table); struct Job* read_DB(int jobid, const char* table); int read_jobid_DB(int** jobids, const char* table); int delete_DB(int jobid, const char* table); -void movetop_DB(int jobid); -void swap_DB(int, int); -void set_jobids_DB(int value); +int movetop_DB(int jobid); +int swap_DB(int, int); +int set_jobids_DB(int value); int get_jobids_DB(); -void set_state_DB(int jobid, int state); +int set_state_DB(int jobid, int state); // int jobDB_num, jobDB_wait_num; // struct Job** jobDB_Jobs; diff --git a/sqlite.c b/sqlite.c index b820b05..3cb3004 100644 --- a/sqlite.c +++ b/sqlite.c @@ -7,6 +7,7 @@ #include "main.h" sqlite3 *db = NULL; +char sql[1024*16] = ""; const char *get_sqlite_path() { char *str; @@ -36,8 +37,7 @@ static int callback(void *max, int argc, char **argv, char **azColName) { return 0; } -static int check_order_id(const char *op) { - char sql[100]; +static int check_order_id(const char *op, int* err) { char *err_msg = NULL; sprintf(sql, "SELECT %s(order_id) FROM Jobs", op); int value = 0; @@ -45,31 +45,35 @@ static int check_order_id(const char *op) { if (rc != SQLITE_OK) { fprintf(stderr, "[check_order_id] SQL error: %s, sql: %s\n", sql, err_msg); sqlite3_free(err_msg); + err[0] = -1; + } else { + err[0] = 0; } return value; } -static int max_order_id() { return check_order_id("MAX"); } +static int max_order_id(int* err) { return check_order_id("MAX", err); } -static int min_order_id() { return check_order_id("MIN"); } +static int min_order_id(int* err) { return check_order_id("MIN", err); } -static int get_order_id(int jobid) { +static int get_order_id(int jobid, int* err) { char *err_msg = 0; - char sql[1024]; int value = 0; sprintf(sql, "SELECT order_id FROM Jobs WHERE jobid=%d", jobid); int rc = sqlite3_exec(db, sql, callback, &value, &err_msg); if (rc != SQLITE_OK) { fprintf(stderr, "[get_order_id] SQL error: %s\n", err_msg); sqlite3_free(err_msg); - return 0; + err[0] = -1; + } else { + err[0] = 0; } return value; } -void close_sqlite() { +int close_sqlite() { // free(jobDB_Jobs); - sqlite3_close(db); + return sqlite3_close(db); } int open_sqlite() { @@ -77,11 +81,12 @@ int open_sqlite() { char *zErrMsg = 0; int rc; rc = sqlite3_open(path, &db); - + int error_flag = 0; + if (rc) { printf("Can't open database: %s\n", sqlite3_errmsg(db)); sqlite3_close(db); - return (1); + return (-1); } char *sql = @@ -119,6 +124,7 @@ int open_sqlite() { if (rc != SQLITE_OK) { printf("[open_sqlite0] SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); + error_flag--; } else { printf("Table Jobs created successfully\n"); } @@ -158,6 +164,7 @@ int open_sqlite() { if (rc != SQLITE_OK) { printf("[open_sqlite1] SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); + error_flag--; } else { printf("Table Finished created successfully\n"); } @@ -170,9 +177,10 @@ int open_sqlite() { if (rc != SQLITE_OK) { printf("[open_sqlite2] SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); + //error_flag--; } - return 0; + return error_flag; } int get_jobids_DB() { @@ -183,25 +191,27 @@ int get_jobids_DB() { if (rc != SQLITE_OK) { fprintf(stderr, "[get_jobids_DB] SQL error: %s\n", err_msg); sqlite3_free(err_msg); - return 1000; + return 1000; // default value } return value; } -void set_jobids_DB(int value) { +// return error code +int set_jobids_DB(int value) { char *err_msg = 0; - char sql[1024]; sprintf(sql, "INSERT OR REPLACE INTO Global (id, JOBIDs) VALUES (1, %d);", value); int rc = sqlite3_exec(db, sql, 0, 0, &err_msg); if (rc != SQLITE_OK) { fprintf(stderr, "[set_jobids_DB] SQL error: %s\n", err_msg); sqlite3_free(err_msg); + return -1; } + return 0; } +// return error code int delete_DB(int jobid, const char *table) { - char sql[1024]; sprintf(sql, "DELETE FROM %s WHERE jobid=%d;", table, jobid); char *errmsg = NULL; @@ -218,12 +228,11 @@ static int edit_DB(struct Job *job, const char *table, const char *action) { struct Procinfo *info = &(job->info); const char *label = job->label == NULL ? "(..)" : job->label; const char *email = job->email == NULL ? "(..)" : job->email; - - char sql[1024]; - - int order_id = get_order_id(job->jobid); - if (order_id == 0) { - order_id = max_order_id() + 1; + int err = 0; + int order_id = get_order_id(job->jobid, &err); + if (err != 0) { + order_id = max_order_id(&err) + 1; + if (err !=0) return -1; } char *depend_on = ints_to_chars(job->depend_on_size, job->depend_on, ","); char *notify_errorlevel_to = ints_to_chars(job->notify_errorlevel_to_size, @@ -275,40 +284,55 @@ int insert_or_replace_DB(struct Job *job, const char *table) { return edit_DB(job, table, "INSERT OR REPLACE"); } -static void set_order_id_DB(int jobid, int order_id) { +//return error code +static int set_order_id_DB(int jobid, int order_id) { char *err_msg = 0; - char sql[1024]; sprintf(sql, "UPDATE Jobs SET order_id=%d WHERE jobid=%d", order_id, jobid); int rc = sqlite3_exec(db, sql, 0, 0, &err_msg); if (rc != SQLITE_OK) { fprintf(stderr, "[movetop_DB] SQL error: %s\n", err_msg); sqlite3_free(err_msg); + return -1; } + return 0; } -void set_state_DB(int jobid, int state) { +//return error code +int set_state_DB(int jobid, int state) { char *err_msg = 0; - char sql[1024]; sprintf(sql, "UPDATE Jobs SET state=%d WHERE jobid=%d", state, jobid); int rc = sqlite3_exec(db, sql, 0, 0, &err_msg); if (rc != SQLITE_OK) { fprintf(stderr, "[movetop_DB] SQL error: %s\n", err_msg); sqlite3_free(err_msg); + return -1; } + return 0; } -void swap_DB(int jobid0, int jobid1) { - int id0 = get_order_id(jobid0); - int id1 = get_order_id(jobid1); - set_order_id_DB(jobid0, id1); - set_order_id_DB(jobid1, id0); +int swap_DB(int jobid0, int jobid1) { + int err0, err1, err = 0; + int id0 = get_order_id(jobid0, &err0); + int id1 = get_order_id(jobid1, &err1); + if (err0 == 0 && err1 == 0) { + err0 = set_order_id_DB(jobid0, id1); + err1 = set_order_id_DB(jobid1, id0); + if (err0 != 0 || err1 != 0) { + err = -1; + } + } else { + err = -1; + } + return err; } -void movetop_DB(int jobid) { - int order_id = min_order_id() - 1; - if (order_id == 0) - order_id = -1; - set_order_id_DB(jobid, order_id); +int movetop_DB(int jobid) { + int err; + int order_id = min_order_id(&err) - 1; + if (err != 0) { + return err; + } + return set_order_id_DB(jobid, order_id); } /* @@ -323,9 +347,10 @@ static void clear_DB(const char* table) { } } */ + +// return error code int read_jobid_DB(int **jobids, const char *table) { int n; - char sql[1024]; sprintf(sql, "SELECT COUNT(*) FROM %s;", table); char *errmsg = NULL; @@ -342,7 +367,7 @@ int read_jobid_DB(int **jobids, const char *table) { if (rc != SQLITE_OK) { fprintf(stderr, "[read_jobid_DB1] SQL error: %s by %s\n", sqlite3_errmsg(db), sql); - return -2; // 返回-1表示查询失败 + return -2; // 返回-2表示查询失败 } if (sqlite3_step(stmt) == SQLITE_ROW) { @@ -377,7 +402,6 @@ struct Job *read_DB(int jobid, const char *table) { struct Result *result = &(job->result); struct Procinfo *info = &(job->info); - char sql[2048]; sprintf(sql, "SELECT * FROM %s WHERE jobid=%d;", table, jobid); char *errmsg = NULL; From 4720a0e3698301a9f0fc405c0a1af17acc9a6860 Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Fri, 23 Feb 2024 14:30:04 +0800 Subject: [PATCH 84/91] add command length check for input --- client.c | 4 ++-- execute.c | 6 +++--- jobs.c | 4 ++-- main.c | 16 ++++++++++++++++ sqlite.c | 3 +-- 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/client.c b/client.c index f193c87..c698a04 100644 --- a/client.c +++ b/client.c @@ -58,12 +58,12 @@ char *charArray_string(int num, char** array) { void c_new_job() { // printf("new _job \n"); struct Msg m = default_msg(); - char *new_command, path[1024]; + char *new_command, path[2048]; char *myenv; m.type = NEWJOB; - getcwd(path, 1024); + getcwd(path, 2048); new_command = command_line.linux_cmd; // build_command_string(); char* old_command = build_command_string(); diff --git a/execute.c b/execute.c index 74236ac..c12ad3f 100644 --- a/execute.c +++ b/execute.c @@ -102,7 +102,7 @@ static void run_relink(int pid, struct Result *result) { if (client_uid == 0) { status = ptrace_pid(pid); /* - char buff[1024]; + char buff[]; sprintf(buff, "strace -e none -e exit_group -p %d", pid); status = system(buff); // sprintf(buff, "%d", pid); @@ -382,8 +382,8 @@ int run_job(int jobid, struct Result *res) { int pid; int errorlevel = 0; int p[2]; - char path[1024]; - getcwd(path, 1024); + char path[2048]; + getcwd(path, 2048); // const char *tmpdir = get_logdir(); // printf("tmpdir: %s\n", tmpdir); diff --git a/jobs.c b/jobs.c index cbef8e9..995fda2 100644 --- a/jobs.c +++ b/jobs.c @@ -90,8 +90,8 @@ static void send_mail_via_ssmtp(struct Job *p) { ? "failed" : "finished"; const char *unit = time_rep(&real_ms); - char cmd[1024]; - snprintf(cmd, 1023, + char cmd[2048]; + snprintf(cmd, 2047, "echo \"Subject: %s[%d] n_core: %d, Elsp %.3f %s from MSI\nFrom: " "TS<%s>\nTo: %s\n\n\n Cmd: %s [%s] Output: %s\" | ssmtp %s", p->label, p->jobid, p->num_slots, real_ms, unit, p->email, diff --git a/main.c b/main.c index 734000c..2040fb7 100644 --- a/main.c +++ b/main.c @@ -22,6 +22,7 @@ #include "user.h" int client_uid; +const int MAX_LEN = 1024 * 10; extern char *optarg; extern int optind, opterr, optopt; @@ -529,6 +530,21 @@ void parse_opts(int argc, char **argv) { if (optind < argc && command_line.request == c_LIST) { command_line.request = c_QUEUE; get_command(optind, argc, argv); + // check_length + int n_len = 0; + for (size_t i = 0; i < command_line.command.num; i++) + { + n_len += strnlen(command_line.command.array[i], 1024); + } + if (n_len > MAX_LEN) { + printf("too long command:"); + for (size_t i = 0; i < command_line.command.num; i++) + { + printf("%s ", command_line.command.array[i]); + } + printf(" with %d chars, Max. %d", n_len, MAX_LEN); + } + } command_line.linux_cmd = charArray_string(argc, argv); diff --git a/sqlite.c b/sqlite.c index 3cb3004..e2c5053 100644 --- a/sqlite.c +++ b/sqlite.c @@ -177,7 +177,7 @@ int open_sqlite() { if (rc != SQLITE_OK) { printf("[open_sqlite2] SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); - //error_flag--; + // error_flag--; } return error_flag; @@ -337,7 +337,6 @@ int movetop_DB(int jobid) { /* static void clear_DB(const char* table) { - char sql[1024]; char* err_msg; sprintf(sql, "DELETE FROM %s", table); int rc = sqlite3_exec(db, sql, 0, 0, &err_msg); From 4c088a1475f32b26e1f325b41852bb6e64297cfb Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Wed, 6 Mar 2024 02:02:09 +0800 Subject: [PATCH 85/91] add function to pause process recursively --- jobs.c | 144 +++++++++++++++++++++++++++++++++---------------------- list.c | 6 ++- main.h | 6 ++- server.c | 1 + user.c | 117 ++++++++++++++++++++++++++++++-------------- 5 files changed, 178 insertions(+), 96 deletions(-) diff --git a/jobs.c b/jobs.c index 995fda2..fc88af8 100644 --- a/jobs.c +++ b/jobs.c @@ -53,6 +53,7 @@ int max_jobs; static struct Job *get_job(int jobid); static int fork_cmd(int UID, const char *path, const char *cmd); +static int safe_pause_pid(struct Job *p); void notify_errorlevel(struct Job *p); @@ -157,14 +158,15 @@ static int allocate_cores_ex(struct Job *p, const char *extra) { if (p == NULL) return 0; - if (p->state == RUNNING || p->state == LOCKED) { + if (p->state == RUNNING || p->state == PAUSE) { #ifdef TASKSET set_task_cores(p, extra); #else - kill_pid(p->pid, extra, NULL); + kill_all_process(p->pid, SIGCONT); + // kill_pid(p->pid, extra, NULL); #endif } - + if (extra == 0 || is_sleep(p->pid) == 0) { int ts_UID = p->ts_UID; user_busy[ts_UID] += p->num_slots; @@ -208,7 +210,8 @@ void check_pause() { int pid = paused_pids[i]; int res = is_sleep(pid); if (res == 0) { - kill_pid(pid, "kill -s STOP", NULL); + kill_all_process(pid, SIGSTOP); + // kill_pid(pid, "kill -s STOP", NULL); } else if (res == -1) { num_pause--; paused_pids[i] = paused_pids[num_pause]; @@ -683,7 +686,7 @@ void s_mark_job_running(int jobid) { } if (is_sleep(p->pid) == 1) { set_pause(p->pid); - p->state = RUNNING; + p->state = PAUSE; return; } } @@ -738,6 +741,11 @@ const char *jstate2string(enum Jobstate s) { case LOCKED: jobstate = "locked "; break; + case PAUSE: + jobstate = "holdon "; + break; + default: + jobstate = "UNKNOWN "; } return jobstate; } @@ -1635,6 +1643,18 @@ void s_clear_finished(int ts_UID) { other_user_job->next = NULL; } +void s_check_holdjob() { + struct Job *p = firstjob.next; + while (p != NULL) { + if (p->pid != 0 && p->state == PAUSE) { + if (is_sleep(p->pid) == 0) { + kill_all_process(p->pid, SIGSTOP); + } + } + p = p->next; + } +} + // run the jobs void s_process_runjob_ok(int jobid, char *oname, int pid) { // printf("s_process_runjob_ok \n "); @@ -1642,7 +1662,7 @@ void s_process_runjob_ok(int jobid, char *oname, int pid) { p = findjob(jobid); if (p == 0) error("Job %i already run not found on runjob_ok", jobid); - if (p->state != RUNNING) + if (p->state != RUNNING && p->state != PAUSE) error("Job %i not running, but %i on runjob_ok", jobid, p->state); p->pid = pid; @@ -1654,7 +1674,9 @@ void s_process_runjob_ok(int jobid, char *oname, int pid) { write_logfile(p); set_task_cores(p, NULL); } - insert_or_replace_DB(p, "Jobs"); + if (p->state == RUNNING) { + insert_or_replace_DB(p, "Jobs"); + } } void s_send_runjob(int s, int jobid) { @@ -1738,8 +1760,7 @@ void s_job_info(int s, int jobid) { fd_nprintf(s, 100, "\n"); fd_nprintf(s, 100, "User: %s [%d]\n", user_name[p->ts_UID], user_UID[p->ts_UID]); - fd_nprintf(s, 100, "State: %9s PID: %-6d\n", jstate2string(p->state), - p->pid); + fd_nprintf(s, 100, "State: %9s PID: %-6d\n", jstate2string(p->state), p->pid); #ifdef TASKSET if (p->cores != NULL) { @@ -1764,7 +1785,7 @@ void s_job_info(int s, int jobid) { if (p->email) { fd_nprintf(s, 100, "Email: %s\n", p->email); } - + if (p->state == RUNNING) { t = pinfo_time_until_now(&p->info); } else if (p->state == FINISHED) { @@ -1772,7 +1793,8 @@ void s_job_info(int s, int jobid) { fd_nprintf(s, 100, "End time: %s", ctime(&p->info.end_time.tv_sec)); } const char *unit = time_rep(&t); - if (t > 0) fd_nprintf(s, 100, "Time running: %.4f %s\n", t, unit); + if (t > 0) + fd_nprintf(s, 100, "Time running: %.4f %s\n", t, unit); if (p->state == FINISHED) { struct Result *res = &(p->result); fd_nprintf(s, 100, "Error: %d Signal: %d Die: %d\n", res->errorlevel, @@ -1816,19 +1838,13 @@ void s_resume_user(int s, int ts_UID) { struct Job *p = firstjob.next; while (p != NULL) { - if (p->ts_UID == ts_UID && p->state == LOCKED) { + if (p->ts_UID == ts_UID && p->state == PAUSE) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { printf("pid = %d\n", p->pid); - if (is_sleep(p->pid) == 1) { - printf("allocate = %d\n", p->pid); - int status = allocate_cores_ex(p, "kill -s CONT"); - if (status == 1) { - p->state = RUNNING; - free_pause(p->pid); - } - } - // kill_pid(p->pid, "kill -s CONT"); + allocate_cores_ex(p, "kill -s CONT"); + p->state = RUNNING; + free_pause(p->pid); } } p = p->next; @@ -1851,14 +1867,8 @@ void s_suspend_user(int s, int ts_UID) { if (p->ts_UID == ts_UID && p->state == RUNNING) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { - if (is_sleep(p->pid) == 0) { - kill_pid(p->pid, "kill -s STOP", NULL); - if (is_sleep(p->pid) == 1) { - p->state = LOCKED; - free_cores(p); - set_pause(p->pid); - } - } + safe_pause_pid(p); + p->state = PAUSE; } else { char *label = "(...)"; if (p->label != NULL) @@ -2203,6 +2213,19 @@ static void s_unlock_queue(struct Job *p) { } } +static int safe_pause_pid(struct Job *p) { + kill(p->pid, SIGSTOP); + kill_all_process(p->pid, SIGSTOP); + if (is_sleep(p->pid) == 1) { + set_pause(p->pid); + free_cores(p); + return 0; + } else { + kill_all_process(p->pid, SIGCONT); + return 1; + } +} + void s_hold_job(int s, int jobid, int ts_UID) { if (user_max_slots[ts_UID] < 0) { snprintf(buff, 255, "Error: The owner `%s` is locked\n", user_name[ts_UID]); @@ -2237,20 +2260,22 @@ void s_hold_job(int s, int jobid, int ts_UID) { return; } - if (is_sleep(p->pid) == 1) { - snprintf(buff, 255, "job [%d] is aleady in PAUSE.\n", jobid); + if (p->state == PAUSE) { + snprintf(buff, 255, "job [%d] is aleady in HOLDON.\n", jobid); send_list_line(s, buff); return; } int job_tsUID = p->ts_UID; if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { - kill_pid(p->pid, "kill -s STOP", NULL); - if (is_sleep(p->pid) == 1) { - set_pause(p->pid); - free_cores(p); + // kill_pid(p->pid, "kill -s STOP", NULL); + if (safe_pause_pid(p) == 0) { + p->state = PAUSE; + snprintf(buff, 255, "To pause job [%d] successfully!\n", jobid); + } else { + snprintf(buff, 255, "Error: cannot pause job [%d] using kill SIGSTOP\n", + jobid); } - snprintf(buff, 255, "To pause job [%d] successfully!\n", jobid); } else { snprintf(buff, 255, "Error: cannot pause job [%d]\n", jobid); } @@ -2292,31 +2317,34 @@ void s_cont_job(int s, int jobid, int ts_UID) { return; } - if (is_sleep(p->pid) == 0) { - snprintf(buff, 255, "job [%d] is aleady in RUNNING.\n", jobid); - send_list_line(s, buff); - return; - } - - int job_tsUID = p->ts_UID; - if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { - int num_slots = p->num_slots; - if (user_busy[ts_UID] + num_slots <= user_max_slots[ts_UID] && - busy_slots + num_slots <= max_slots) { - - int status = allocate_cores_ex(p, "kill -s CONT"); - if (status == 1) { - p->state = RUNNING; - free_pause(p->pid); - } - snprintf(buff, 255, "To rerun job [%d] successfully!\n", jobid); + if (p->state == RUNNING) { + if (is_sleep(p->pid) == 0) { + snprintf(buff, 255, "job [%d] is aleady in RUNNING.\n", jobid); } else { - snprintf(buff, 255, "Error: not enough slots [%d]\n", jobid); + kill_all_process(p->pid, SIGCONT); + snprintf(buff, 255, "job [%d] is continued.\n", jobid); } } else { - snprintf(buff, 255, "Error: cannot rerun job [%d]\n", jobid); - } - // kill_pid(p->pid, "kill -s CONT", NULL); + int job_tsUID = p->ts_UID; + if (p->pid != 0 && (job_tsUID = ts_UID || ts_UID == 0)) { + int num_slots = p->num_slots; + if (user_busy[ts_UID] + num_slots <= user_max_slots[ts_UID] && + busy_slots + num_slots <= max_slots) { + + int status = allocate_cores_ex(p, "kill -s CONT"); + if (status == 1) { + p->state = RUNNING; + free_pause(p->pid); + } + snprintf(buff, 255, "To rerun job [%d] successfully!\n", jobid); + } else { + snprintf(buff, 255, "Error: not enough slots [%d]\n", jobid); + } + } else { + snprintf(buff, 255, "Error: cannot rerun job [%d]\n", jobid); + } + } // p->pid + send_list_line(s, buff); } /* Don't complain, if the socket doesn't exist */ diff --git a/list.c b/list.c index eafd8ea..3e072ae 100644 --- a/list.c +++ b/list.c @@ -139,10 +139,14 @@ static char *print_noresult(const struct Job *p) { jobstate = "N/A"; } else { if (is_sleep(p->pid) == 1) { - jobstate = "pause"; + jobstate = "sleep "; } } } + if (p->state == PAUSE && is_sleep(p->pid) == 1) { + jobstate = "pause "; // TODO delete this + } + output_filename = ofilename_shown(p); char *uname = user_name[p->ts_UID]; diff --git a/main.h b/main.h index fea0ecb..6fedb29 100644 --- a/main.h +++ b/main.h @@ -157,7 +157,8 @@ struct Msg; enum Jobstate { QUEUED, - RUNNING, + RUNNING, + PAUSE, FINISHED, SKIPPED, HOLDING_CLIENT, @@ -551,6 +552,8 @@ void debug_write(const char *str); const char *uid2user_name(int uid); int read_first_jobid_from_logfile(const char *path); void kill_pid(int ppid, const char *signal, const char* extra); +void kill_all_process(int ppid, int signal); + // char* linux_cmd(char* CMD, char* out, int out_size); char **split_str(const char *str, int *size); void check_relink(int pid); @@ -587,6 +590,7 @@ void check_pause(); void free_pause_array(); struct Job *findjob(int jobid); void setup_ssmtp(); +void s_check_holdjob(); /* client.c */ void c_list_jobs_all(); diff --git a/server.c b/server.c index c155d5a..9643b45 100644 --- a/server.c +++ b/server.c @@ -395,6 +395,7 @@ static void server_loop(int ls) { } // printf("end of next_run_job for jobid[%d]\n", newjob); } // job != -1 + s_check_holdjob(); } // end of while (keep_loop) end_server(ls); diff --git a/user.c b/user.c index 5bb2bc7..f895030 100644 --- a/user.c +++ b/user.c @@ -1,16 +1,18 @@ #define _GNU_SOURCE +#include #include +#include #include #include -#include -#include #include #include +#include +#include +#include "default.inc" #include "main.h" #include "user.h" -#include "default.inc" void send_list_line(int s, const char *str); void error(const char *str, ...); @@ -27,7 +29,6 @@ const char *get_user_path() { } } - int get_env(const char *env, int v0) { char *str; str = getenv(env); @@ -43,23 +44,24 @@ int get_env(const char *env, int v0) { //按空格自动分割子串的函数 char **split_str(const char *str0, int *size) { - char **result = (char**)malloc(sizeof(char*)); //存储分割后的子串 - char *str = (char*) malloc(sizeof(char)*strlen(str0)); - strcpy(str, str0); - int n = 0; //数组的大小 - char *token; //分割得到的子串 - token = strtok(str, " "); //以空格为分隔符分割字符串 - while (token != NULL) { //循环分割,直到遇到NULL - result = realloc(result, (n + 1) * sizeof(char *)); //重新分配内存空间,增加一个元素 - if (result == NULL) { //如果内存分配失败,返回NULL - return NULL; - } - result[n] = token; //将子串存入数组 - n++; //更新数组的大小 - token = strtok(NULL, " "); //继续分割 + char **result = (char **)malloc(sizeof(char *)); //存储分割后的子串 + char *str = (char *)malloc(sizeof(char) * strlen(str0)); + strcpy(str, str0); + int n = 0; //数组的大小 + char *token; //分割得到的子串 + token = strtok(str, " "); //以空格为分隔符分割字符串 + while (token != NULL) { //循环分割,直到遇到NULL + result = realloc(result, + (n + 1) * sizeof(char *)); //重新分配内存空间,增加一个元素 + if (result == NULL) { //如果内存分配失败,返回NULL + return NULL; } - *size = n; //返回数组的大小 - return result; //返回数组 + result[n] = token; //将子串存入数组 + n++; //更新数组的大小 + token = strtok(NULL, " "); //继续分割 + } + *size = n; //返回数组的大小 + return result; //返回数组 } /* @@ -259,10 +261,11 @@ void s_user_status_all(int s) { send_list_line(s, "-- Users ----------- \n"); for (int i = 0; i < user_number; i++) { extra = user_locked[i] != 0 ? "Locked" : ""; - if (user_max_slots[i] == 0 && user_busy[i] == 0) continue; - snprintf(buffer, 256, "[%04d] %3d/%-4d Q:%-3d %16s Run. %2d %s\n", user_UID[i], - user_busy[i], abs(user_max_slots[i]), user_queue[i], user_name[i], user_jobs[i], - extra); + if (user_max_slots[i] == 0 && user_busy[i] == 0) + continue; + snprintf(buffer, 256, "[%04d] %3d/%-4d Q:%-3d %16s Run. %2d %s\n", + user_UID[i], user_busy[i], abs(user_max_slots[i]), user_queue[i], + user_name[i], user_jobs[i], extra); send_list_line(s, buffer); } snprintf(buffer, 256, "Service at UID:%d\n", server_uid); @@ -273,11 +276,11 @@ void s_user_status_all(int s) { void s_user_status(int s, int i) { char buffer[256]; char *extra = ""; - if (user_locked[i] != 0) + if (user_locked[i] != 0) extra = "Locked"; - snprintf(buffer, 256, "[%04d] %3d/%-4d Q:%-3d %16s Run. %2d %s\n", user_UID[i], - user_busy[i], abs(user_max_slots[i]), user_queue[i], user_name[i], user_jobs[i], - extra); + snprintf(buffer, 256, "[%04d] %3d/%-4d Q:%-3d %16s Run. %2d %s\n", + user_UID[i], user_busy[i], abs(user_max_slots[i]), user_queue[i], + user_name[i], user_jobs[i], extra); send_list_line(s, buffer); } @@ -290,26 +293,68 @@ int get_tsUID(int uid) { return -1; } -void kill_pid(int pid, const char *signal, const char* extra) { - if (signal == NULL && extra == NULL) return; +void kill_all_process(int parent_pid, int signal) { + char path[256]; + DIR *dir; + struct dirent *entry; + + snprintf(path, sizeof(path), "/proc/%d/task", parent_pid); + + if ((dir = opendir(path)) == NULL) { + printf("Cannot find PID from %s\n", path); + return; + } + + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + int tid = atoi(entry->d_name); + printf("TID: %d\n", tid); + snprintf(path, sizeof(path), "/proc/%d/task/%d/children", parent_pid, tid); + + FILE *file = fopen(path, "r"); + if (path == NULL) { + printf("cannot open %s\n", path); + continue; + } + int cPID; + while (fscanf(file, "%d", &cPID) == 1) { + printf("Children PID: %d\n", cPID); + kill_all_process(cPID, signal); + } + fclose(file); + } + closedir(dir); + + if (kill(parent_pid, signal) != 0) { + printf("kill %d -%d ", parent_pid, signal); + perror("Error resuming process"); + } +} + +void kill_pid(int pid, const char *signal, const char *extra) { + if (signal == NULL && extra == NULL) + return; const char *path = get_kill_sh_path(); - char* command = NULL; + char *command = NULL; int size = strnlen(path, 1000) + strlen(signal) + 100; if (extra == NULL) { - command = (char*) malloc(size); + command = (char *)malloc(size); sprintf(command, "bash %s %d \"%s\"", path, pid, signal); } else { - command = (char*) malloc(size + strlen(extra)); + command = (char *)malloc(size + strlen(extra)); sprintf(command, "bash %s %d \"%s\" \"%s\"", path, pid, signal, extra); } printf("command = %s\n", command); // fp = popen(command, "r"); - // FILE* f = fopen("/home/kylin/task-spooler/file.log", "a"); - //fprintf(f, "%s\n", command); + // FILE* f = fopen("/home/kylin/task-spooler/file.log", "a"); + // fprintf(f, "%s\n", command); system(command); - //fclose(f); + // fclose(f); free(command); // pclose(fp); } From d4ee779efe7b24fe4e3ad3f6ab9e5d1782bc3857 Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Wed, 6 Mar 2024 08:26:22 +0800 Subject: [PATCH 86/91] remove the extra bash and fix the taskset --- Makefile | 2 +- jobs.c | 87 +++++++++++--------------------------------------- list.c | 2 +- main.h | 8 ++--- server.c | 5 +-- server_start.c | 87 -------------------------------------------------- taskset.c | 14 +++----- user.c | 42 ++++++++---------------- 8 files changed, 43 insertions(+), 204 deletions(-) diff --git a/Makefile b/Makefile index c3afe03..d1bd839 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ PREFIX?=/usr/local PREFIX_LOCAL=~ GLIBCFLAGS=#-D_XOPEN_SOURCE=500 -D__STRICT_ANSI__ CPPFLAGS+=$(GLIBCFLAGS) -CFLAGS?=-pedantic -ansi -Wall -g -std=gnu11 -DTASKSET_ -DSOUND -fcommon -Wno-format-truncation +CFLAGS?=-pedantic -ansi -Wall -g -std=gnu11 -DTASKSET -DSOUND -fcommon -Wno-format-truncation OBJECTS=main.o \ server.o \ server_start.o \ diff --git a/jobs.c b/jobs.c index fc88af8..0de5486 100644 --- a/jobs.c +++ b/jobs.c @@ -154,20 +154,22 @@ static void free_cores(struct Job *p) { #endif } -static int allocate_cores_ex(struct Job *p, const char *extra) { +static int allocate_cores(struct Job *p) { if (p == NULL) return 0; if (p->state == RUNNING || p->state == PAUSE) { #ifdef TASKSET - set_task_cores(p, extra); + set_task_cores(p); #else - kill_all_process(p->pid, SIGCONT); + if (is_sleep(p->pid)) { + kill_pids(p->pid, SIGCONT, NULL); + } // kill_pid(p->pid, extra, NULL); #endif } - if (extra == 0 || is_sleep(p->pid) == 0) { + if (is_sleep(p->pid) == 0) { int ts_UID = p->ts_UID; user_busy[ts_UID] += p->num_slots; busy_slots += p->num_slots; @@ -178,55 +180,6 @@ static int allocate_cores_ex(struct Job *p, const char *extra) { return 0; } -static void allocate_cores(struct Job *p) { allocate_cores_ex(p, NULL); } - -int num_pause; -int *paused_pids = NULL; - -void init_pause() { - num_pause = 0; - paused_pids = (int *)malloc(sizeof(int) * max_jobs); -} - -static int set_pause(int pid) { - paused_pids[num_pause] = pid; - return num_pause++; -} - -static int free_pause(int pid) { - for (int i = 0; i < num_pause; i++) { - if (paused_pids[i] == pid) { - num_pause--; - paused_pids[i] = paused_pids[num_pause]; - return 1; - } - } - return 0; -} - -void check_pause() { - int i = 0; - while (i < num_pause) { - int pid = paused_pids[i]; - int res = is_sleep(pid); - if (res == 0) { - kill_all_process(pid, SIGSTOP); - // kill_pid(pid, "kill -s STOP", NULL); - } else if (res == -1) { - num_pause--; - paused_pids[i] = paused_pids[num_pause]; - continue; - } - i++; - } - // printf("num_pause = %d\n", num_pause); -} - -void free_pause_array() { - free(paused_pids); - paused_pids = NULL; -} - /* Serialize a job and add it to the JSON array. Returns 1 for success, 0 for * failure. */ static int add_job_to_json_array(struct Job *p, cJSON *jobs) { @@ -685,7 +638,6 @@ void s_mark_job_running(int jobid) { p->output_filename = get_ofile_from_FD(p->pid); } if (is_sleep(p->pid) == 1) { - set_pause(p->pid); p->state = PAUSE; return; } @@ -1490,6 +1442,7 @@ void job_finished(const struct Result *result, int jobid) { jpointer->next = newfirst; } } + static int fork_cmd(const int UID, const char *path, const char *cmd) { int pid = -1; //定义一个进程ID变量 @@ -1643,12 +1596,14 @@ void s_clear_finished(int ts_UID) { other_user_job->next = NULL; } -void s_check_holdjob() { - struct Job *p = firstjob.next; - while (p != NULL) { +void s_check_holdon() { + struct Job *p; + /* Show Queued or Running jobs */ + p = firstjob.next; + while (p != 0) { if (p->pid != 0 && p->state == PAUSE) { if (is_sleep(p->pid) == 0) { - kill_all_process(p->pid, SIGSTOP); + kill_pids(p->pid, SIGSTOP, NULL); } } p = p->next; @@ -1672,7 +1627,7 @@ void s_process_runjob_ok(int jobid, char *oname, int pid) { pinfo_set_start_time_check(&p->info); if (pid > 0 && is_sleep(pid) == 0) { write_logfile(p); - set_task_cores(p, NULL); + set_task_cores(p); } if (p->state == RUNNING) { insert_or_replace_DB(p, "Jobs"); @@ -1842,9 +1797,8 @@ void s_resume_user(int s, int ts_UID) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { printf("pid = %d\n", p->pid); - allocate_cores_ex(p, "kill -s CONT"); + allocate_cores(p); p->state = RUNNING; - free_pause(p->pid); } } p = p->next; @@ -2215,13 +2169,12 @@ static void s_unlock_queue(struct Job *p) { static int safe_pause_pid(struct Job *p) { kill(p->pid, SIGSTOP); - kill_all_process(p->pid, SIGSTOP); + kill_pids(p->pid, SIGSTOP, NULL); if (is_sleep(p->pid) == 1) { - set_pause(p->pid); free_cores(p); return 0; } else { - kill_all_process(p->pid, SIGCONT); + kill_pids(p->pid, SIGCONT, NULL); return 1; } } @@ -2321,7 +2274,7 @@ void s_cont_job(int s, int jobid, int ts_UID) { if (is_sleep(p->pid) == 0) { snprintf(buff, 255, "job [%d] is aleady in RUNNING.\n", jobid); } else { - kill_all_process(p->pid, SIGCONT); + kill_pids(p->pid, SIGCONT, NULL); snprintf(buff, 255, "job [%d] is continued.\n", jobid); } } else { @@ -2331,10 +2284,8 @@ void s_cont_job(int s, int jobid, int ts_UID) { if (user_busy[ts_UID] + num_slots <= user_max_slots[ts_UID] && busy_slots + num_slots <= max_slots) { - int status = allocate_cores_ex(p, "kill -s CONT"); - if (status == 1) { + if (allocate_cores(p)) { p->state = RUNNING; - free_pause(p->pid); } snprintf(buff, 255, "To rerun job [%d] successfully!\n", jobid); } else { diff --git a/list.c b/list.c index 3e072ae..e36acbd 100644 --- a/list.c +++ b/list.c @@ -21,7 +21,7 @@ extern int core_usage; /* return 0 for running and 1 for sleep and -1 for error */ int is_sleep(int pid) { - // if (pid == 0) return -1; + if (pid == 0) return -1; char filename[256]; char name[256]; char status = '\0'; diff --git a/main.h b/main.h index 6fedb29..7a8c40e 100644 --- a/main.h +++ b/main.h @@ -551,8 +551,7 @@ long str2int(const char *str); void debug_write(const char *str); const char *uid2user_name(int uid); int read_first_jobid_from_logfile(const char *path); -void kill_pid(int ppid, const char *signal, const char* extra); -void kill_all_process(int ppid, int signal); +void kill_pids(int ppid, int signal, const char* cmd); // char* linux_cmd(char* CMD, char* out, int out_size); char **split_str(const char *str, int *size); @@ -586,11 +585,10 @@ int s_check_relink(int s, int pid, int ts_UID); void s_read_sqlite(); int s_check_running_pid(int pid); void init_pause(); -void check_pause(); +void s_check_holdon(); void free_pause_array(); struct Job *findjob(int jobid); void setup_ssmtp(); -void s_check_holdjob(); /* client.c */ void c_list_jobs_all(); @@ -626,5 +624,5 @@ char* insert_chars_check(int pos, const char* input, const char* c); /* taskset.c */ void init_taskset(); -int set_task_cores(struct Job* p, const char* extra); +int set_task_cores(struct Job* p); void unlock_core_by_job(struct Job* p); diff --git a/server.c b/server.c index 9643b45..c076187 100644 --- a/server.c +++ b/server.c @@ -284,7 +284,6 @@ void server_main(int notify_fd, char *_path) { // debug_write("Cannot open sqlite database"); error("Cannot open sqlite database"); } - init_pause(); // printf("jobids = %d\n", get_jobids_DB()); jobsort_flag = get_env("TS_SORTJOBS", 0); s_set_jobids(get_env("TS_FIRST_JOBID", get_jobids_DB())); @@ -380,7 +379,6 @@ static void server_loop(int ls) { // printf("end of next_run, newjob = %d\n", newjob); if (newjob != -1) { - check_pause(); int conn, awaken_job; conn = get_conn_of_jobid(newjob); /* This next marks the firstjob state to RUNNING */ @@ -395,7 +393,7 @@ static void server_loop(int ls) { } // printf("end of next_run_job for jobid[%d]\n", newjob); } // job != -1 - s_check_holdjob(); + s_check_holdon(); } // end of while (keep_loop) end_server(ls); @@ -405,7 +403,6 @@ static void end_server(int ls) { close(ls); unlink(path); close_sqlite(); - free_pause_array(); /* This comes from the parent, in the fork after server_main. * This is the last use of path in this process.*/ free(path); diff --git a/server_start.c b/server_start.c index 3e4fc71..984b753 100644 --- a/server_start.c +++ b/server_start.c @@ -19,96 +19,11 @@ #include "main.h" int server_socket; -char kill_sh_path[1024] = {0}; - static char *socket_path; static int should_check_owner = 0; static int fork_server(); -const char *get_kill_sh_path() { return kill_sh_path; } - -static const char *set_kill_sh_path() { - char *tmpdir; - tmpdir = getenv("TMPDIR"); - if (tmpdir == NULL) - tmpdir = "/tmp"; - // char *path = NULL; - int size = strlen(tmpdir) + strlen("/kill_ppid.sh") + 1; - // path = (char *)malloc(size); - snprintf(kill_sh_path, size, "%s/kill_ppid.sh", tmpdir); - return kill_sh_path; -} - -static void setup_kill_sh() { - set_kill_sh_path(); - const char *path = get_kill_sh_path(); - FILE *f = fopen(path, "w"); - if (f == NULL) { - printf("Cannot create `kill_ppide.sh` file at %s\n", path); - exit(0); - } - const char *script = - "#!/bin/bash\n\n" - "# getting children generally resolves nicely at some point\n" - "get_child() {\n" - " echo $(pgrep -laP $1 | awk '{print $1}')\n" - "}\n\n" - "get_children() {\n" - " __RET=$(get_child $1)\n" - " __CHILDREN=\n" - " while [ -n \"$__RET\" ]; do\n" - " __CHILDREN+=\"$__RET \"\n" - " __RET=$(get_child $__RET)\n" - " done\n\n" - " __CHILDREN=$(echo \"${__CHILDREN}\" | xargs | sort)\n\n" - " echo \"${__CHILDREN} $1\"\n" - "}\n\n" - "if [ 1 -gt $# ]; \n" - "then\n" - " echo \"not input PID\"\n" - " exit 1\n" - "fi\n\n" - "owner=`ps -o user= -p $1`\n" - "if [ -z \"$owner\" ]; \n" - "then\n" - " // echo \"not a valid PID\"\n" - " exit 1\n" - "fi\n" - "pids=`get_children $1`\n\n" - "user=`whoami`\n\n" - "extra=\"\"\n" - "if [[ \"$owner\" != \"$user\" ]]; then\n" - " extra=\"sudo\"\n" - "fi\n\n" - "if [ -z \"$3\" ]\n" - "then\n" - " if [ -z \"$2\" ]\n" - " then\n" - " for pid in ${pids};\n" - " do\n" - " ${extra} echo ${pid}\n" - " done\n" - " else\n" - " for pid in ${pids};\n" - " do\n" - " ${extra} $2 ${pid}\n" - " done\n" - " fi\n" - "else\n" - " for pid in ${pids};\n" - " do\n" - " ${extra} $2 ${pid}\n" - " ${extra} $3 ${pid}\n" - " done\n" - "fi;\n"; - - fprintf(f, "%s", script); - fclose(f); - - printf(" Kill_PPID.sh at `%s`\n\n", path); -} - void create_socket_path(char **path) { char *tmpdir; char userid[20] = "root"; @@ -206,8 +121,6 @@ static void server_info() { printf(" Read user file from %s [TS_USER_PATH]\n", get_user_path()); printf(" Write log file to %s [TS_LOGFILE_PATH]\n", set_server_logfile()); printf(" Sqlite Database @ %s [TS_SQLITE_PATH]\n", get_sqlite_path()); - - setup_kill_sh(); } static void server_daemon() { diff --git a/taskset.c b/taskset.c index a936e6e..c93ae56 100644 --- a/taskset.c +++ b/taskset.c @@ -28,7 +28,7 @@ void init_taskset() { #endif } -int allocate_cores(int N) { +static int allocate_cores(int N) { if (N + core_usage > MAX_CORE_NUM) return 0; task_core_num = 0; int i = 0; @@ -68,7 +68,7 @@ void unlock_core_by_job(struct Job* p) { #endif } -int set_task_cores(struct Job* p, const char* extra) { +int set_task_cores(struct Job* p) { if (p == NULL || p->pid <= 0) return -1; if (p->taskset_flag == 0) return 0; #ifdef TASKSET @@ -81,18 +81,12 @@ int set_task_cores(struct Job* p, const char* extra) { lock_core_by_job(p); char* core_str = ints_to_chars(N, task_cores_id, ","); - int size = strlen(core_str) + 50; + int size = strlen(core_str) + 30; char* cmd = (char*) malloc(sizeof(char) * size); sprintf(cmd, "taskset -cp %s ", core_str); - if (extra == NULL) { - ; // printf("[CMD] %s %d\n", cmd, p->pid); - } else { - printf("[CMD] %s %d; %s %d\n", cmd, p->pid, extra, p->pid); - } - kill_pid(p->pid, cmd, extra); + kill_pids(p->pid, -1, cmd); p->cores = core_str; - // free(core_str); free(cmd); #endif return 0; diff --git a/user.c b/user.c index f895030..5b54189 100644 --- a/user.c +++ b/user.c @@ -293,7 +293,7 @@ int get_tsUID(int uid) { return -1; } -void kill_all_process(int parent_pid, int signal) { +void kill_pids(int parent_pid, int signal, const char* cmd) { char path[256]; DIR *dir; struct dirent *entry; @@ -301,7 +301,7 @@ void kill_all_process(int parent_pid, int signal) { snprintf(path, sizeof(path), "/proc/%d/task", parent_pid); if ((dir = opendir(path)) == NULL) { - printf("Cannot find PID from %s\n", path); + // printf("Cannot find PID from %s\n", path); return; } @@ -311,7 +311,7 @@ void kill_all_process(int parent_pid, int signal) { } int tid = atoi(entry->d_name); - printf("TID: %d\n", tid); + // printf("TID: %d\n", tid); snprintf(path, sizeof(path), "/proc/%d/task/%d/children", parent_pid, tid); FILE *file = fopen(path, "r"); @@ -321,40 +321,26 @@ void kill_all_process(int parent_pid, int signal) { } int cPID; while (fscanf(file, "%d", &cPID) == 1) { - printf("Children PID: %d\n", cPID); - kill_all_process(cPID, signal); + // printf("Children PID: %d\n", cPID); + kill_pids(cPID, signal, cmd); } fclose(file); } closedir(dir); - if (kill(parent_pid, signal) != 0) { + if (signal >= 0 && kill(parent_pid, signal) != 0) { printf("kill %d -%d ", parent_pid, signal); perror("Error resuming process"); } -} - -void kill_pid(int pid, const char *signal, const char *extra) { - if (signal == NULL && extra == NULL) - return; - const char *path = get_kill_sh_path(); - char *command = NULL; - int size = strnlen(path, 1000) + strlen(signal) + 100; + if (cmd != NULL) { + char buf[1024] = ""; + snprintf(buf, 1024, "%s %d", cmd, parent_pid); + printf("%s\n", buf); + int result = system(buf); - if (extra == NULL) { - command = (char *)malloc(size); - sprintf(command, "bash %s %d \"%s\"", path, pid, signal); - } else { - command = (char *)malloc(size + strlen(extra)); - sprintf(command, "bash %s %d \"%s\" \"%s\"", path, pid, signal, extra); + if (result == -1) { + ; // perror("Error executing command"); + } } - printf("command = %s\n", command); - // fp = popen(command, "r"); - // FILE* f = fopen("/home/kylin/task-spooler/file.log", "a"); - // fprintf(f, "%s\n", command); - system(command); - // fclose(f); - free(command); - // pclose(fp); } From e0fbf1c561fad4c1d894e761d0181993fd7897a9 Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Wed, 6 Mar 2024 11:17:09 +0800 Subject: [PATCH 87/91] add macro for no taskset mode --- Makefile | 2 +- taskset.c | 144 ++++++++++++++++++++++++++++++------------------------ 2 files changed, 80 insertions(+), 66 deletions(-) diff --git a/Makefile b/Makefile index d1bd839..03f835b 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ PREFIX?=/usr/local PREFIX_LOCAL=~ GLIBCFLAGS=#-D_XOPEN_SOURCE=500 -D__STRICT_ANSI__ CPPFLAGS+=$(GLIBCFLAGS) -CFLAGS?=-pedantic -ansi -Wall -g -std=gnu11 -DTASKSET -DSOUND -fcommon -Wno-format-truncation +CFLAGS?=-pedantic -ansi -Wall -g -std=gnu11 -D_TASKSET -DSOUND -fcommon -Wno-format-truncation OBJECTS=main.o \ server.o \ server_start.o \ diff --git a/taskset.c b/taskset.c index c93ae56..adc388a 100644 --- a/taskset.c +++ b/taskset.c @@ -1,95 +1,109 @@ -#include #include +#include #include #include "main.h" -#define MAX_CORE_NUM 16 // 256 +#define MAX_CORE_NUM 16 // 256 #define MAX_CORE_NUM_HALF 8 // 128 #define MAX_CORE_NUM_QUAD 4 // 64 +#ifdef TASKSET static int core_id[MAX_CORE_NUM]; -// = {0,64,1,65,2,66,3,67,4,68,5,69,6,70,7,71,8,72,9,73,10,74,11,75,12,76,13,77,14,78,15,79,16,80,17,81,18,82,19,83,20,84,21,85,22,86,23,87,24,88,25,89,26,90,27,91,28,92,29,93,30,94,31,95,32,96,33,97,34,98,35,99,36,100,37,101,38,102,39,103,40,104,41,105,42,106,43,107,44,108,45,109,46,110,47,111,48,112,49,113,50,114,51,115,52,116,53,117,54,118,55,119,56,120,57,121,58,122,59,123,60,124,61,125,62,126,63,127,128,192,129,193,130,194,131,195,132,196,133,197,134,198,135,199,136,200,137,201,138,202,139,203,140,204,141,205,142,206,143,207,144,208,145,209,146,210,147,211,148,212,149,213,150,214,151,215,152,216,153,217,154,218,155,219,156,220,157,221,158,222,159,223,160,224,161,225,162,226,163,227,164,228,165,229,166,230,167,231,168,232,169,233,170,234,171,235,172,236,173,237,174,238,175,239,176,240,177,241,178,242,179,243,180,244,181,245,182,246,183,247,184,248,185,249,186,250,187,251,188,252,189,253,190,254,191,255}; +// = +// {0,64,1,65,2,66,3,67,4,68,5,69,6,70,7,71,8,72,9,73,10,74,11,75,12,76,13,77,14,78,15,79,16,80,17,81,18,82,19,83,20,84,21,85,22,86,23,87,24,88,25,89,26,90,27,91,28,92,29,93,30,94,31,95,32,96,33,97,34,98,35,99,36,100,37,101,38,102,39,103,40,104,41,105,42,106,43,107,44,108,45,109,46,110,47,111,48,112,49,113,50,114,51,115,52,116,53,117,54,118,55,119,56,120,57,121,58,122,59,123,60,124,61,125,62,126,63,127,128,192,129,193,130,194,131,195,132,196,133,197,134,198,135,199,136,200,137,201,138,202,139,203,140,204,141,205,142,206,143,207,144,208,145,209,146,210,147,211,148,212,149,213,150,214,151,215,152,216,153,217,154,218,155,219,156,220,157,221,158,222,159,223,160,224,161,225,162,226,163,227,164,228,165,229,166,230,167,231,168,232,169,233,170,234,171,235,172,236,173,237,174,238,175,239,176,240,177,241,178,242,179,243,180,244,181,245,182,246,183,247,184,248,185,249,186,250,187,251,188,252,189,253,190,254,191,255}; +static struct Job *core_jobs[MAX_CORE_NUM] = {NULL}; +#endif -static struct Job* core_jobs[MAX_CORE_NUM] = { NULL }; int task_cores_id[MAX_CORE_NUM] = {0}; int task_array_id[MAX_CORE_NUM] = {0}; int task_core_num, core_usage; void init_taskset() { - // printf("CPU taskset()\n"); - task_core_num = 0; - core_usage = 0; - #ifdef TASKSET - for (int i = 0; i < MAX_CORE_NUM; i++) { - core_id[i] = i / 2 + ((i % 2) + (i >= MAX_CORE_NUM_HALF)) * MAX_CORE_NUM_QUAD; - printf("[%3d] => %3d\t", i, core_id[i]); - if ((i+1)%8 == 0) printf("\n"); - } - #endif +#ifdef TASKSET + // printf("CPU taskset()\n"); + task_core_num = 0; + core_usage = 0; + for (int i = 0; i < MAX_CORE_NUM; i++) { + core_id[i] = + i / 2 + ((i % 2) + (i >= MAX_CORE_NUM_HALF)) * MAX_CORE_NUM_QUAD; + printf("[%3d] => %3d\t", i, core_id[i]); + if ((i + 1) % 8 == 0) + printf("\n"); + } +#endif } + +#ifdef TASKSET + static int allocate_cores(int N) { - if (N + core_usage > MAX_CORE_NUM) return 0; - task_core_num = 0; - int i = 0; - while(task_core_num < N && i < MAX_CORE_NUM) { - if (core_jobs[i] == NULL) { - task_cores_id[task_core_num] = core_id[i]; - task_array_id[task_core_num] = i; - task_core_num++; - } - i++; + task_core_num = 0; + if (N + core_usage > MAX_CORE_NUM) + return 0; + int i = 0; + while (task_core_num < N && i < MAX_CORE_NUM) { + if (core_jobs[i] == NULL) { + task_cores_id[task_core_num] = core_id[i]; + task_array_id[task_core_num] = i; + task_core_num++; } - if (task_core_num != N) task_core_num = 0; - return task_core_num; + i++; + } + if (task_core_num != N) + task_core_num = 0; + return task_core_num; } -void lock_core_by_job(struct Job* p) { - if (p == NULL) return; - for (int i = 0; i < task_core_num; i++) { - int iA = task_array_id[i]; - core_jobs[iA] = p; - } - core_usage += task_core_num; - task_core_num = 0; +void lock_core_by_job(struct Job *p) { + if (p == NULL) + return; + for (int i = 0; i < task_core_num; i++) { + int iA = task_array_id[i]; + core_jobs[iA] = p; + } + core_usage += task_core_num; + task_core_num = 0; } +#endif -void unlock_core_by_job(struct Job* p) { - #ifdef TASKSET - if (p == NULL || p->taskset_flag == 0) return; - for (int i = 0; i < MAX_CORE_NUM; i++) { - if (core_jobs[i] == p) { - core_jobs[i] = NULL; - core_usage--; - } + +void unlock_core_by_job(struct Job *p) { +#ifdef TASKSET + if (p == NULL || p->taskset_flag == 0) + return; + for (int i = 0; i < MAX_CORE_NUM; i++) { + if (core_jobs[i] == p) { + core_jobs[i] = NULL; + core_usage--; } - free(p->cores); - p->cores = NULL; - #endif + } + free(p->cores); + p->cores = NULL; +#endif } -int set_task_cores(struct Job* p) { - if (p == NULL || p->pid <= 0) return -1; - if (p->taskset_flag == 0) return 0; +int set_task_cores(struct Job *p) { + if (p == NULL || p->pid <= 0) + return -1; + if (p->taskset_flag == 0) + return 0; #ifdef TASKSET - int N = p->num_slots; + int N = p->num_slots; - if (allocate_cores(N) != N) { - printf("cannot allocate %d cores\n", N); - return -1; - } - lock_core_by_job(p); - - char* core_str = ints_to_chars(N, task_cores_id, ","); - int size = strlen(core_str) + 30; - char* cmd = (char*) malloc(sizeof(char) * size); - sprintf(cmd, "taskset -cp %s ", core_str); + if (allocate_cores(N) != N) { + printf("cannot allocate %d cores\n", N); + return -1; + } + lock_core_by_job(p); + + char *core_str = ints_to_chars(N, task_cores_id, ","); + int size = strlen(core_str) + 30; + char *cmd = (char *)malloc(sizeof(char) * size); + sprintf(cmd, "taskset -cp %s ", core_str); - kill_pids(p->pid, -1, cmd); - p->cores = core_str; - free(cmd); + kill_pids(p->pid, -1, cmd); + p->cores = core_str; + free(cmd); #endif - return 0; + return 0; } - - From ca214463bf8f9c1c17adb9514ec0593dae4d27f6 Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Wed, 6 Mar 2024 19:07:45 +0800 Subject: [PATCH 88/91] fixed the bugs --- Makefile | 2 +- jobs.c | 75 +++++++++++++++++++++++++++----------------------------- 2 files changed, 37 insertions(+), 40 deletions(-) diff --git a/Makefile b/Makefile index 03f835b..c2ee973 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ PREFIX?=/usr/local PREFIX_LOCAL=~ GLIBCFLAGS=#-D_XOPEN_SOURCE=500 -D__STRICT_ANSI__ CPPFLAGS+=$(GLIBCFLAGS) -CFLAGS?=-pedantic -ansi -Wall -g -std=gnu11 -D_TASKSET -DSOUND -fcommon -Wno-format-truncation +CFLAGS?=-pedantic -ansi -Wall -g -std=gnu11 -DNO_TASKSET -DSOUND -fcommon -Wno-format-truncation OBJECTS=main.o \ server.o \ server_start.o \ diff --git a/jobs.c b/jobs.c index 0de5486..b23a3ab 100644 --- a/jobs.c +++ b/jobs.c @@ -154,29 +154,23 @@ static void free_cores(struct Job *p) { #endif } -static int allocate_cores(struct Job *p) { - if (p == NULL) - return 0; +static int config_running(struct Job *p) { + if (p == NULL || (p->state != PAUSE && p->state != QUEUED)) return 1; - if (p->state == RUNNING || p->state == PAUSE) { #ifdef TASKSET set_task_cores(p); -#else - if (is_sleep(p->pid)) { - kill_pids(p->pid, SIGCONT, NULL); - } - // kill_pid(p->pid, extra, NULL); #endif - } - if (is_sleep(p->pid) == 0) { - int ts_UID = p->ts_UID; - user_busy[ts_UID] += p->num_slots; - busy_slots += p->num_slots; - p->num_allocated = p->num_slots; - user_jobs[ts_UID]++; - return 1; + if (is_sleep(p->pid)) { + kill_pids(p->pid, SIGCONT, NULL); } + + int ts_UID = p->ts_UID; + user_busy[ts_UID] += p->num_slots; + busy_slots += p->num_slots; + p->num_allocated = p->num_slots; + user_jobs[ts_UID]++; + p->state = RUNNING; return 0; } @@ -640,18 +634,13 @@ void s_mark_job_running(int jobid) { if (is_sleep(p->pid) == 1) { p->state = PAUSE; return; + } else { + p->state = QUEUED; } } - - /* - int ts_UID = p->ts_UID; - user_busy[ts_UID] += p->num_slots; - busy_slots += p->num_slots; - user_jobs[ts_UID]++; - p->num_allocated = p->num_slots - */ - allocate_cores(p); - p->state = RUNNING; + if (config_running(p)) { + error("Err. in s_mark_job_running(): Cannot mark Job %d as RUNNING from state %i\n", jobid, p->state); + } } /* -1 means nothing awaken, otherwise returns the jobid awaken */ @@ -1612,12 +1601,15 @@ void s_check_holdon() { // run the jobs void s_process_runjob_ok(int jobid, char *oname, int pid) { - // printf("s_process_runjob_ok \n "); + struct Job *p; p = findjob(jobid); if (p == 0) error("Job %i already run not found on runjob_ok", jobid); - if (p->state != RUNNING && p->state != PAUSE) + if (p->state == PAUSE) { + return; + } + if (p->state != RUNNING) error("Job %i not running, but %i on runjob_ok", jobid, p->state); p->pid = pid; @@ -1625,13 +1617,15 @@ void s_process_runjob_ok(int jobid, char *oname, int pid) { p->output_filename = oname; } pinfo_set_start_time_check(&p->info); - if (pid > 0 && is_sleep(pid) == 0) { + if (pid > 0) { + // printf("s_process_runjob_ok = %d\n", jobid); write_logfile(p); - set_task_cores(p); - } - if (p->state == RUNNING) { + //if (p->state == PAUSE) { + // config_running(p); + //} insert_or_replace_DB(p, "Jobs"); } + } void s_send_runjob(int s, int jobid) { @@ -1710,12 +1704,16 @@ void s_job_info(int s, int jobid) { fd_nprintf(s, 100, ",%i", p->depend_on[i]); fd_nprintf(s, 100, "]&& "); } + const char* status = ""; + if (p->state != PAUSE && is_sleep(p->pid)) { + status = " in SLEEP!"; + } write(s, p->command + p->command_strip, strlen(p->command + p->command_strip)); fd_nprintf(s, 100, "\n"); fd_nprintf(s, 100, "User: %s [%d]\n", user_name[p->ts_UID], user_UID[p->ts_UID]); - fd_nprintf(s, 100, "State: %9s PID: %-6d\n", jstate2string(p->state), p->pid); + fd_nprintf(s, 100, "State: %9s PID: %-6d%s\n", jstate2string(p->state), p->pid, status); #ifdef TASKSET if (p->cores != NULL) { @@ -1796,9 +1794,8 @@ void s_resume_user(int s, int ts_UID) { if (p->ts_UID == ts_UID && p->state == PAUSE) { // p->state = HOLDING_CLIENT; if (p->pid != 0) { - printf("pid = %d\n", p->pid); - allocate_cores(p); - p->state = RUNNING; + // printf("pid = %d\n", p->pid); + config_running(p); } } p = p->next; @@ -2284,8 +2281,8 @@ void s_cont_job(int s, int jobid, int ts_UID) { if (user_busy[ts_UID] + num_slots <= user_max_slots[ts_UID] && busy_slots + num_slots <= max_slots) { - if (allocate_cores(p)) { - p->state = RUNNING; + if (config_running(p)) { + printf("Cannot set Job %i as RUNNING", p->jobid); } snprintf(buff, 255, "To rerun job [%d] successfully!\n", jobid); } else { From 230ebcdb682c0cd62a63d741766199d533939715 Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Sun, 23 Mar 2025 20:05:48 +0800 Subject: [PATCH 89/91] add lock time-out and slots usage refresh and better help menu --- README.md | 223 +++++++++++++++++++++------------------- default.inc | 1 + jobs.c | 23 +++++ main.c | 219 +++++++++++++++------------------------ notifications-sound.wav | Bin 0 -> 242756 bytes server.c | 15 ++- version.h | 2 +- 7 files changed, 235 insertions(+), 248 deletions(-) diff --git a/README.md b/README.md index 2e96707..ecb2fa9 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ # Task Spooler PLUS -This project is a fork of Task Spooler by [Task Spooler by Lluís Batlle i Rossell](https://vicerveza.homeunix.net/~viric/soft/ts/)., a software that offers basic task management. I have enhanced this software with more useful features, such as **multiple user support, fatal crash recovery, and processor allocation and binding.** The aim of this project is to provide an instant, standalone task management system, unlike *SLURM* and *PBS* which have complex installation and dependency issues. This task-spooler-PLUS is suitable for task management on PC and workstation with several to tens of users. +This project builds upon [Task Spooler by Lluís Batlle i Rossell](https://vicerveza.homeunix.net/~viric/soft/ts/), a software providing fundamental task management capabilities. The software has been enhanced with additional features of practical significance, including support for **multiple users, recovery from fatal crashes, and processor allocation and binding**. The objective of this project is to offer a self-contained, immediately operational task management system. In contrast to systems such as *SLURM* and *PBS*, which are encumbered by complex installation procedures and dependency challenges, this task-spooler-PLUS is designed for effective task management on personal computers and workstations accommodating several to tens of users. ## Introduction -As a computer scientist, I often need to submit several to tens of simulation tasks on my own workstations and share the computational resources with other users. I tried the original task-spooler software, but it did not support multiple users. Everyone had their own task queue. Therefore, I modified the task-spooler and renamed it as **task-spooler-PLUS** to provide multiple user support. Recently, I also added fatal crash recovery and processor binding features. After a fatal crash, the task-spooler-PLUS can read the data from *Sqlite3* to recover all tasks, including running, queued, and finished ones. The processor binding is done through the *taskset* command. Unlike the original version, the task-spooler-PLUS server needs to run in the background with root privileges. +As a computer scientist, I frequently need to submit multiple—sometimes dozens—of simulation tasks on my personal workstations while sharing computational resources with other users. I initially experimented with the original task-spooler software, but it lacked multi-user support, as each user maintained an independent task queue. To address this limitation, I modified the software and developed task-spooler-PLUS, enabling support for multiple users. + +More recently, I introduced two key enhancements: **fatal crash recovery and processor binding**. In the event of a fatal crash, task-spooler-PLUS utilizes *SQLite3* to restore all tasks, including those that were running, queued, or completed. Additionally, processor binding is implemented via the *taskset* command, allowing for more efficient resource allocation. Unlike the original version, task-spooler-PLUS operates as a background service and **requires root privileges.** ### Changelog @@ -12,18 +14,16 @@ See [CHANGELOG](CHANGELOG.md). ## Features -I enhanced the Task Spooler to run tasks on my workstation with multiple users. The following are the features of task-spooler-PLUS. - -* Task queue management for GNU/Linux, Darwin, Cygwin, and FreeBSD -* Multiple user support with the different limits on the maximum processors usage -* Fatal crush recovery by reading and writing the task log into Sqlite3 database -* Ability To pause and rerun any running or queued task -* Ability To stop or continue all tasks by a single user -* Good information output (default, json, and tab) -* Easy installation and configuration -* Optional separation of stdout and stderr +I have enhanced task-spooler to support task execution on my workstation with multiple users. Below are the key features of task-spooler-PLUS: -## Setup +- **Cross-platform task queue management** for GNU/Linux, Darwin, Cygwin, and FreeBSD +- **Multi-user support** with customizable limits on maximum processor usage +- **Fatal crash recovery**, ensuring task persistence by logging data to an *SQLite3* database +- **Pause and resume functionality** for any running or queued task +- **Global control** to stop or resume all tasks for a single user +- **Comprehensive information output**, available in default, JSON, and tab-separated formats +- **Simple installation and configuration** for ease of use +- **Optional separation of stdout and stderr** for better log management ### Install Task Spooler PLUS @@ -32,7 +32,7 @@ Simple run the provided script ``` ./make ``` -if you don't need the processors binding feature, try to remove `-DTASKSET` option of `CFLAGS`. +if you don't need the **taskset** processors binding feature, try to add`-DNO_TASKSET` option of `CFLAGS`. **The default positions** of log file and database is defined in `default.inc`. @@ -40,20 +40,28 @@ if you don't need the processors binding feature, try to remove `-DTASKSET` opti #define DEFAULT_USER_PATH "/home/kylin/task-spooler/user.txt" #define DEFAULT_LOG_PATH "/home/kylin/task-spooler/log.txt" #define DEFAULT_SQLITE_PATH "/home/kylin/task-spooler/task-spooler.db" +#define DEFAULT_EMAIL_SENDER "kylincaster@foxmail.com" +#define DEFAULT_EMAIL_TIME 45.0 +#define DEFAULT_USER_LOCK_TIME 5 +#define DEFAULT_HPC_NAME "intel_laptop" + +enum { MAXCONN = 1000 }; +enum { DEFAULT_MAXFINISHED = 1000 }; + #define DEFAULT_NOTIFICATION_SOUND "/home/kylin/task-spooler/notifications-sound.wav" #define DEFAULT_ERROR_SOUND "/home/kylin/task-spooler/error.wav" -#define DEFAULT_PULSE_SERVER "unix:/mnt/wslg/PulseServer" -#define DEFAULT_EMAIL_SENDER "kylincaster@foxmail.com" -#define DEFAULT_EMAIL_TIME 45.0% +#define DEFAULT_PULSE_SERVER "·" ``` -You can specific the positions by the environment variables `TS_USER_PATH`, `TS_LOGFILE_PATH`, and `TS_SQLITE_PATH`, respectively on the invoking of daemon server. Otherwise, you could specify the positions in the `user config` file. +You can specific the positions by the environment variables `TS_USER_PATH`, `TS_LOGFILE_PATH`, and `TS_SQLITE_PATH`, respectively on the invoking of daemon server. Otherwise, you could specify the positions in the `user_config` file. + +In `taskset.c`, **the processor binding sequence** is determined by three variables: `MAX_CORE_NUM`,`MAX_CORE_NUM_HALF`, and `MAX_CORE_NUM_QUAD`. -In `taskset.c`, **the sequence of processors binding** is determined by there variables `MAX_CORE_NUM`,`MAX_CORE_NUM_HALF`, and `MAX_CORE_NUM_QUAD`. `MAX_CORE_NUM` defines the total number of processors in your computer. +​ `MAX_CORE_NUM` represents the total number of processors available on the system. -For a personal laptop with 2 CPU, and each CPU have 4 cores and 8 logical processors, by Hyper-threading. The optimal configuration would be: +For a personal laptop with **two CPUs**, each equipped with **four physical cores** and **eight logical processors** via Hyper-Threading, the optimal configuration would be: ``` #define MAX_CORE_NUM 16 @@ -86,7 +94,6 @@ static int core_id[MAX_CORE_NUM] = {0, 4, 1, 5, 2, 6, 3, 7} To use `ts` anywhere, `ts` needs to be added to `$PATH` if it hasn't been done already. -To use `man`, you may also need to add `$HOME/.local/share/man` to `$MANPATH`. #### Common problems * Once the suspending of the task-spooler Plus client: try to remove the socket file `/tmp/socket-ts.root` define by `TS_SOCKET` @@ -94,11 +101,11 @@ To use `man`, you may also need to add `$HOME/.local/share/man` to `$MANPATH`. ## User configuration -using the `TS_USER_PATH` environment variable to specify the path to the user configuration. The format of the user file is shown as an example. The UID could be found by `id -u [user]`. +using the `TS_USER_PATH` environment variable to specify the path to the user configuration. The format of the user config file is shown as an example. The UID could be found by `id -u [user]`. ``` # 1231 # comments -TS_SLOTS = 4 # The number of slots +TS_SLOTS = 4 # The number of Total slots # uid name slots 1000 Kylin 10 3021 test1 10 @@ -108,23 +115,17 @@ TS_SLOTS = 4 # The number of slots qweq qweq qweq # error, automatically skipped ``` -Note that the number of slots could be specified in the user configuration file (2nd line). - -## Mailing list - -I created a GoogleGroup for the program. You look for the archive and the join methods in the taskspooler google group page. - -Alessandro Öhler once maintained a mailing list for discussing newer functionalities and interchanging use experiences. I think this doesn't work anymore, but you can look at the old archive or even try to subscribe. +Note that the number of slots can be specified in the user configuration file (2nd line). ## How it works -The queue is maintained by a server process. This server process is started if it isn't there already. The communication goes through a unix socket usually in /tmp/. +The queue is managed by a *server process*, which is automatically started if it is not already running. Communication between the client and server occurs via a Unix socket, typically located in `/tmp/`. -When the user requests a job (using a ts client), the client waits for the server message to know when it can start. When the server allows starting , this client usually forks, and runs the command with the proper environment, because the client runs run the job and not the server, like in 'at' or 'cron'. So, the ulimits, environment, pwd,. apply. +When a user submits a job using a ts client, the client waits for a response from the server to determine when execution can begin. Once the server grants permission, the client typically *forks* and executes the command within the appropriate environment. Unlike `at` or `cron`, the client, not the server, runs the job. As a result, user-specific settings such as ulimits, environment variables, and working directory (pwd) are applied. -When the job finishes, the client notifies the server. At this time, the server may notify any waiting client, and stores the output and the errorlevel of the finished job. +Upon job completion, the client *notifies the server*, which may then signal waiting clients and record both the *job's output and exit status*. -Moreover the client can take advantage of many information from the server: when a job finishes, where does the job output go to, etc. +Additionally, clients can retrieve various details from the server, such as *job completion status and output locations*, enabling better monitoring and management of queued tasks. ## History @@ -149,87 +150,93 @@ Kylin wrote the multiple user support, fatal crush recovery through Sqlite3 data See below or `man ts` for more details. ``` -.1.0 - a task queue system for the unix user. -Copyright (C) 2007-2023 Kylin JIANG - Duc Nguyen - Lluis Batlle i Rossell -usage: ts [action] [-ngfmdE] [-L ] [-D ] [cmd...] -Env vars: - TS_SOCKET the path to the unix socket used by the ts command. - TS_MAIL_FROM who send the result mail, default (kylincaster@foxmail.com) - TS_MAIL_TIME the duration criterion to send a email, default (45.000 sec) - TS_MAXFINISHED maximum finished jobs in the queue. - TS_MAXCONN maximum number of ts connections at once. - TS_ONFINISH binary called on job end (passes jobid, error, outfile, command). - TS_ENV command called on enqueue. Its output determines the job information. - TS_SAVELIST filename which will store the list, if the server dies. - TS_SLOTS amount of jobs which can run at once, read on server start. - TS_USER_PATH path to the user configuration file, read on server starts. - TS_LOGFILE_PATH path to the job log file, read on server starts - TS_SQLITE_PATH path to the job log file, read on server starts - TS_FIRST_JOBID The first job ID (default: 1000), read on server starts. - TS_SORTJOBS Switch to control the job sequence sort, read on server starts. - TMPDIR directory where to place the output files and the default socket. +Task Spooler 2.1.1a - a task queue system for the unix user. +Copyright (C) 2007-2024 Kylin JIANG - Duc Nguyen - Lluis Batlle i Rossell +usage: ./ts [action] [-ngfmdE] [-L ] [-D ] [cmd...] + +Environment Variables: + TS_SOCKET : Unix socket path (default: $TMPDIR/socket-ts.root) + TS_MAIL_FROM : Sender email for results (default: kylincaster@foxmail.com) + TS_MAIL_TIME : Email threshold in seconds (default: 45.000 sec.) + TS_SERVICE_NAME : Service name for Email notifications (default: intel_laptop) + TS_MAXFINISHED : Max finished jobs in queue (default: 1000) + TS_MAXCONN : Max concurrent connections (max 1000, default: 1000) TS_ONFINISH : Binary executed post-job (args: ID, status, output, cmd) + TS_ENV : Command to gather job info during enqueue + TS_SAVELIST : Crash recovery file for job list + TS_SLOTS : Max concurrent jobs (server start, default: 1) + TS_USER_PATH : User config file path (server start) + TS_LOGFILE_PATH : Job log path (server start) + TS_SQLITE_PATH : SQLite DB path for logs (server start) + TS_FIRST_JOBID : Initial job ID (server start, default: 1000) + TS_SORTJOBS : Job queue sorting control (server start) + TMPDIR : Temporary Output files directory + Long option actions: - --getenv [var] get the value of the specified variable in server environment. - --setenv [var] set the specified flag to server environment. - --unsetenv [var] remove the specified flag from server environment. - --get_label || -a [id] show the job label. Of the last added, if not specified. - --full_cmd || -F [id] show full command. Of the last added, if not specified. - --check_daemon Check the daemon is running or not. --count_running || -R return the number of running jobs - --last_queue_id || -q show the job ID of the last added. - --get_logdir Retrieve the path where log files are stored. - --set_logdir [path] Set the path for storing log files. - --serialize || -M [format] Serialize the job list to the specified format. Options: {default, json, tab}. - --daemon Run the server as a daemon (Root access only). - --tmp save the logfile to tmp folder - --hold [jobid] Pause a specific task by its job ID. - --cont [jobid] Resume a paused task by its job ID. - --suspend [user] For regular users, pause all tasks and lock the user account. - For root user, lock all user accounts or a specific user's account. - --resume [user] For regular users, resume all paused tasks and unlock the user account. - For root user, unlock all user accounts or a specific user's account. - --lock Lock the server (Timeout: 30 seconds). For root user, there is no timeout. - --unlock Unlock the server. - --relink [PID] Relink running tasks using their [PID] in case of an unexpected failure. - --job [joibid] || -J [joibid] set the jobid of the new or relink job + --getenv [var] Get server environment variable + --setenv [var] Set server environment flag + --unsetenv [var] Remove server environment flag + --get_label || -a [id] Show job label (last added if unspecified) + --full_cmd || -F [id] Show full command (last added if unspecified) + --check_daemon Verify daemon status + --count_running || -R Count running jobs + --last_queue_id || -q Show last added job ID + --get_logdir Display log directory path + --set_logdir [path] Configure log directory + --serialize || -M [format] Export job list (formats: default/json/tab) + --tmp Store logs in tmp folder + --hold [jobid] Pause specified job + --cont [jobid] Resume paused job + --suspend [user] User: pause tasks & lock account + Root: lock all/specific user account + --resume [user] User: resume tasks & unlock account + Root: unlock all/specific user accounts + --lock Lock server (5 sec. timeout; root has no timeout) + --unlock Release server lock + --relink [PID] Reconnect tasks after unexpected failures + --no-taskset Disable taskset + --job [joibid] || -J [joibid] Assign/relink job ID + --daemon Run as daemon (root only) + Actions: - -A Display information for all users. - -X Update user configuration by UID (Max. 100 users, root access only) - -K Terminate the task spooler server (root access only) - -C Clear the list of finished jobs for the current user. + -A List info for all users + -X Update user config by UID (root only, max 100 users) + -K Stop server (root only) + -C Clear finished jobs for current user -l Show the job list (default action). - -S [num] Get/Set the maximum number of simultaneous server jobs (root access only). - -t [id] "tail -n 10 -f" the output of the job. Last run if not specified. - -c [id] like -t, but shows all the lines. Last run if not specified. - -p [id] show the PID of the job. Last run if not specified. - -o [id] show the output file. Of last job run, if not specified. - -i [id] show job information. Of last job run, if not specified. - -s [id] show the job state. Of the last added, if not specified. - -r [id] remove a job. The last added, if not specified. - -w [id] wait for a job. The last added, if not specified. - -k [id] send SIGTERM to the job process group. The last run, if not specified. - -T send SIGTERM to all running job groups. (only available for root) - -u [id] put that job first. The last added, if not specified. - -U swap two jobs in the queue. - -h | --help show this help - -V show the program version + -S [num] Get/set max concurrent jobs (root only) + -t [id] Tail -f last 10 lines (last job if unspecified) + -c [id] Show complete output (last job if unspecified) + -p [id] Display job PID (last job if unspecified) + -o [id] Show output file path (last job if unspecified) + -i [id] Display job info (last job if unspecified) + -s [id] Show job state (last added if unspecified) + -r [id] Remove job (last added if unspecified) + -w [id] Wait for job (last added if unspecified) + -k [id] Send SIGTERM to job (last run if unspecified) + -T SIGTERM all jobs (root only) + -u [id] Prioritize job (last added if unspecified) + -U Swap two jobs in queue + -h | --help Show help + -V Display version + Options adding jobs: - -B in case of full clients on the server, quit instead of waiting. - -n don't store the output of the command. - -E Keep stderr apart, in a name like the output file, but adding '.e'. - -O Set name of the log file (without any path). - -z gzip the stored output (if not -n). - -f don't fork into background. - -m send the output by e-mail (uses ssmtp). - -d the job will be run after the last job ends. - -D the job will be run after the job of given IDs ends. - -W the job will be run after the job of given IDs ends well (exit code 0). - -L [label] name this task with a label, to be distinguished on listing. - -N [num] number of slots required by the job (1 default). + -B Exit if server full + -n Disable output storage + -E Separate stderr to .e file + -O Set log filename (no path) + -z Gzip output (unless -n) + -f Run in foreground + -m Email results via ssmtp + -d Schedule execution after last job + -D Schedule execution after specified IDs + -W Schedule after successful IDs (exit 0) + -L [label] Assign job label + -N [num] Required slots (default: 1) ``` ## Restore from a fatal crush -Once, the task-spooler-PLUS server is crushed. The service would automatically recover all the tasks. Otherwise, we could do it manually via a automatically python script by `python relink.py`. +If the task-spooler-PLUS server crashes, the service will automatically recover all tasks. Alternatively, manual recovery can be performed using an automated Python script by running `python relink.py`. ``` # relink.py setup @@ -243,7 +250,7 @@ ts -N 10 --relink [pid] task-argv ... ts -L myjob -N 4 --relink [pid] -J [Jobid] task-argv ... ``` -where `[pid]` is the `PID ` of the running task and `[Jobid]` is the specified job id. +where `[pid]` is the *PID* of the running task and `[Jobid]` is the specified job id. **Author** diff --git a/default.inc b/default.inc index 261f572..cc727ed 100644 --- a/default.inc +++ b/default.inc @@ -3,6 +3,7 @@ #define DEFAULT_SQLITE_PATH "/home/kylin/task-spooler/task-spooler.db" #define DEFAULT_EMAIL_SENDER "kylincaster@foxmail.com" #define DEFAULT_EMAIL_TIME 45.0 +#define DEFAULT_USER_LOCK_TIME 5 #define DEFAULT_HPC_NAME "intel_laptop" enum { MAXCONN = 1000 }; diff --git a/jobs.c b/jobs.c index b23a3ab..dbe220d 100644 --- a/jobs.c +++ b/jobs.c @@ -370,6 +370,25 @@ struct Job *findjob(int jobid) { return NULL; } +int s_update_slots_usage() { + int slots_usage = 0; + struct Job *p; + /* Show Queued or Running jobs */ + p = firstjob.next; + while (p != 0) { + if (p->state == RUNNING) { + slots_usage += p->num_slots; + } + p = p->next; + } + if (slots_usage != busy_slots) { + printf("Error: invalid slots: %d vs %d\n", slots_usage, busy_slots); + busy_slots = slots_usage; + } + return slots_usage; +} + + static struct Job *job_by_pid(int pid) { if (pid == 0) return NULL; @@ -939,6 +958,7 @@ static int find_last_stored_jobid_finished() { /* Returns job id or -1 on error */ int s_newjob(int s, struct Msg *m, int ts_UID) { + s_update_slots_usage(); struct Job *p = NULL; int res; @@ -1767,9 +1787,11 @@ void s_send_last_id(int s) { void s_refresh_users(int s) { read_user_file(get_user_path()); send_list_line(s, "refresh the list success!\n"); + s_update_slots_usage(); } void s_suspend_user_all(int s) { + s_update_slots_usage(); for (int i = 1; i < user_number; i++) { s_suspend_user(s, i); } @@ -2099,6 +2121,7 @@ int s_check_locker(int ts_UID) { void s_lock_server(int s, int ts_UID) { if (ts_UID == 0) { + s_update_slots_usage(); user_locker = 0; locker_time = time(NULL); snprintf(buff, 255, "lock the task-spooler server by Root\n"); diff --git a/main.c b/main.c index 2040fb7..5643d58 100644 --- a/main.c +++ b/main.c @@ -41,8 +41,8 @@ static void init_version() { sprintf(version, "Task Spooler %s - a task queue system for the unix user.\n" "Copyright (C) 2007-%d Kylin JIANG - Duc Nguyen - Lluis Batlle i " - "Rossell", - ts_version, 2023); + "Rossell\n", + ts_version, 2024); } static void default_command_line() { @@ -603,145 +603,90 @@ static void go_background() { static void print_help(const char *cmd) { puts(version); - printf("usage: %s [action] [-ngfmdE] [-L ] [-D ] [cmd...]\n", cmd); + printf("\nusage: %s [action] [-ngfmdE] [-L ] [-D ] [cmd...]\n\n", cmd); printf("Environment Variables:\n"); - printf(" TS_SOCKET : The path to the Unix socket used by the 'ts' " - "command (default: $TMPDIR/socket-ts.root)\n"); - printf(" TS_MAIL_FROM : Specifies the sender for result emails " - "(default: %s).\n", - DEFAULT_EMAIL_SENDER); - printf(" TS_MAIL_TIME : Sets the duration criterion to send an email " - "(default: %.3f seconds).\n", - DEFAULT_EMAIL_TIME); - printf(" TS_SERVICE_NAME : Defines the name of the Task-Spooler service in " - "email notifications (default: %s).\n", - DEFAULT_HPC_NAME); - printf(" TS_MAXFINISHED : Specifies the maximum number of finished jobs " - "in the queue (default: %d).\n", DEFAULT_MAXFINISHED); - printf(" TS_MAXCONN : Sets the maximum number of 'ts' connections " - "allowed at once, must less than %d (default: %d).\n", MAXCONN, MAXCONN); - printf(" TS_ONFINISH : Path to a binary called when a job finishes " - "(receives job ID, error status, output file, and command).\n"); - printf(" TS_ENV : Command executed on job enqueue to determine " - "job information.\n"); - printf(" TS_SAVELIST : File path to store the job list in case the " - "server crashes.\n"); - printf(" TS_SLOTS : Defines the maximum number of jobs that can run " - "simultaneously (read on server start with default 1 slot).\n"); - printf(" TS_USER_PATH : Path to the user configuration file (read on " - "server start).\n"); - printf(" TS_LOGFILE_PATH : Path to the job log file (read on server " - "start).\n"); - printf(" TS_SQLITE_PATH : Path to the SQLite database for job logs (read " - "on server start).\n"); - printf(" TS_FIRST_JOBID : Sets the first job ID (default: 1000, read on " - "server start).\n"); - printf(" TS_SORTJOBS : Control the job sequence sorting (read on " - "server start).\n"); - printf(" TMPDIR : Directory where output files and the default " - "socket are placed.\n"); + printf(" TS_SOCKET : Unix socket path (default: $TMPDIR/socket-ts.root)\n"); + printf(" TS_MAIL_FROM : Sender email for results (default: %s)\n", DEFAULT_EMAIL_SENDER); + printf(" TS_MAIL_TIME : Email threshold in seconds (default: %.3f sec.)\n", DEFAULT_EMAIL_TIME); + printf(" TS_SERVICE_NAME : Service name for Email notifications (default: %s)\n", DEFAULT_HPC_NAME); + printf(" TS_MAXFINISHED : Max finished jobs in queue (default: %d)\n", DEFAULT_MAXFINISHED); + printf(" TS_MAXCONN : Max concurrent connections (max %d, default: %d)", MAXCONN, MAXCONN); + printf(" TS_ONFINISH : Binary executed post-job (args: ID, status, output, cmd)\n"); + printf(" TS_ENV : Command to gather job info during enqueue\n"); + printf(" TS_SAVELIST : Crash recovery file for job list\n"); + printf(" TS_SLOTS : Max concurrent jobs (server start, default: 1)\n"); + printf(" TS_USER_PATH : User config file path (server start)\n"); + printf(" TS_LOGFILE_PATH : Job log path (server start)\n"); + printf(" TS_SQLITE_PATH : SQLite DB path for logs (server start)\n"); + printf(" TS_FIRST_JOBID : Initial job ID (server start, default: 1000)\n"); + printf(" TS_SORTJOBS : Job queue sorting control (server start)\n"); + printf(" TMPDIR : Temporary Output files directory\n"); + + printf("\nLong option actions:\n"); + printf(" --getenv [var] Get server environment variable\n"); + printf(" --setenv [var] Set server environment flag\n"); + printf(" --unsetenv [var] Remove server environment flag\n"); + printf(" --get_label || -a [id] Show job label (last added if unspecified)\n"); + printf(" --full_cmd || -F [id] Show full command (last added if unspecified)\n"); + printf(" --check_daemon Verify daemon status\n"); + printf(" --count_running || -R Count running jobs\n"); + printf(" --last_queue_id || -q Show last added job ID\n"); + printf(" --get_logdir Display log directory path\n"); + printf(" --set_logdir [path] Configure log directory\n"); + printf(" --serialize || -M [format] Export job list (formats: default/json/tab)\n"); + printf(" --tmp Store logs in tmp folder\n"); + printf(" --hold [jobid] Pause specified job\n"); + printf(" --cont [jobid] Resume paused job\n"); + + printf(" --suspend [user] User: pause tasks & lock account\n"); + printf(" Root: lock all/specific user account\n"); + + printf(" --resume [user] User: resume tasks & unlock account\n"); + printf(" Root: unlock all/specific user accounts\n"); + + printf(" --lock Lock server (%d sec. timeout; root has no timeout)\n", DEFAULT_USER_LOCK_TIME); + printf(" --unlock Release server lock\n"); + printf(" --relink [PID] Reconnect tasks after unexpected failures\n"); + printf(" --no-taskset Disable taskset\n"); + printf(" --job [joibid] || -J [joibid] Assign/relink job ID\n"); + printf(" --daemon Run as daemon (root only)\n"); - printf("Long option actions:\n"); - printf(" --getenv [var] get the value of the specified " - "variable in server environment.\n"); - printf(" --setenv [var] set the specified flag to server " - "environment.\n"); - printf(" --unsetenv [var] remove the specified flag from " - "server environment.\n"); - printf(" --get_label || -a [id] show the job label. Of the last " - "added, if not specified.\n"); - printf(" --full_cmd || -F [id] show full command. Of the last " - "added, if not specified.\n"); - printf( - " --check_daemon Check the daemon is running or not."); - printf( - " --count_running || -R return the number of running jobs\n"); - printf( - " --last_queue_id || -q show the job ID of the last added.\n"); - printf(" --get_logdir Retrieve the path where log files " - "are stored.\n"); - printf(" --set_logdir [path] Set the path for storing log " - "files.\n"); - printf(" --serialize || -M [format] Serialize the job list to the " - "specified format. Options: {default, json, tab}.\n"); - printf(" --daemon Run the server as a daemon (Root " - "access only).\n"); - printf(" --tmp save the logfile to tmp folder\n"); - printf(" --hold [jobid] Pause a specific task by its job " - "ID.\n"); - printf(" --cont [jobid] Resume a paused task by its job " - "ID.\n"); - printf(" --suspend [user] For regular users, pause all tasks " - "and lock the user account. \n"); - printf(" For root user, lock all user " - "accounts or a specific user's account.\n"); - printf(" --resume [user] For regular users, resume all " - "paused tasks and unlock the user account. \n"); - printf(" For root user, unlock all user " - "accounts or a specific user's account.\n"); - printf(" --lock Lock the server (Timeout: 30 " - "seconds). For root user, there is no timeout.\n"); - printf(" --unlock Unlock the server.\n"); - printf(" --relink [PID] Relink running tasks using their " - "[PID] in case of an unexpected failure.\n"); - printf(" --no-taskset turn off taskset\n"); - printf(" --job [joibid] || -J [joibid] set the jobid of the new or relink " - "job\n"); // printf(" --stime [start_time] Set the relinked task by starting // time (Unix epoch).\n"); - printf("Actions:\n"); - printf(" -A Display information for all users.\n"); - printf(" -X Update user configuration by UID (Max. %d users, " - "root access only)\n", - USER_MAX); - printf( - " -K Terminate the task spooler server (root access only)\n"); - printf( - " -C Clear the list of finished jobs for the current user.\n"); + printf("\nActions:\n"); + printf(" -A List info for all users\n"); + printf(" -X Update user config by UID (root only, max %d users)\n", USER_MAX); + printf(" -K Stop server (root only)\n"); + printf(" -C Clear finished jobs for current user\n"); printf(" -l Show the job list (default action).\n"); - printf(" -S [num] Get/Set the maximum number of simultaneous server " - "jobs (root access only).\n"); - printf(" -t [id] \"tail -n 10 -f\" the output of the job. Last run if " - "not specified.\n"); - printf(" -c [id] like -t, but shows all the lines. Last run if not " - "specified.\n"); - printf( - " -p [id] show the PID of the job. Last run if not specified.\n"); - printf(" -o [id] show the output file. Of last job run, if not " - "specified.\n"); - printf(" -i [id] show job information. Of last job run, if not " - "specified.\n"); - printf(" -s [id] show the job state. Of the last added, if not " - "specified.\n"); - printf(" -r [id] remove a job. The last added, if not specified.\n"); - printf(" -w [id] wait for a job. The last added, if not specified.\n"); - printf(" -k [id] send SIGTERM to the job process group. The last run, " - "if not specified.\n"); - printf(" -T send SIGTERM to all running job groups. (only " - "available for root)\n"); - printf( - " -u [id] put that job first. The last added, if not specified.\n"); - printf(" -U swap two jobs in the queue.\n"); - printf(" -h | --help show this help\n"); - printf(" -V show the program version\n"); - printf("Options adding jobs:\n"); - printf(" -B in case of full clients on the server, quit instead " - "of waiting.\n"); - printf(" -n don't store the output of the command.\n"); - printf(" -E Keep stderr apart, in a name like the output file, " - "but adding '.e'.\n"); - printf(" -O Set name of the log file (without any path).\n"); - printf(" -z gzip the stored output (if not -n).\n"); - printf(" -f don't fork into background.\n"); - printf(" -m send the output by e-mail (uses ssmtp).\n"); - printf(" -d the job will be run after the last job ends.\n"); - printf( - " -D the job will be run after the job of given IDs ends.\n"); - printf(" -W the job will be run after the job of given IDs ends " - "well (exit code 0).\n"); - printf(" -L [label] name this task with a label, to be distinguished on " - "listing.\n"); - printf(" -N [num] number of slots required by the job (1 default).\n"); + printf(" -S [num] Get/set max concurrent jobs (root only)\n"); + printf(" -t [id] Tail -f last 10 lines (last job if unspecified)\n"); + printf(" -c [id] Show complete output (last job if unspecified)\n"); + printf(" -p [id] Display job PID (last job if unspecified)\n"); + printf(" -o [id] Show output file path (last job if unspecified)\n"); + printf(" -i [id] Display job info (last job if unspecified)\n"); + printf(" -s [id] Show job state (last added if unspecified)\n"); + printf(" -r [id] Remove job (last added if unspecified)\n"); + printf(" -w [id] Wait for job (last added if unspecified)\n"); + printf(" -k [id] Send SIGTERM to job (last run if unspecified)\n"); + printf(" -T SIGTERM all jobs (root only)\n"); + printf(" -u [id] Prioritize job (last added if unspecified)\n"); + printf(" -U Swap two jobs in queue\n"); + printf(" -h | --help Show help\n"); + printf(" -V Display version\n"); + printf("\nOptions adding jobs:\n"); + printf(" -B Exit if server full\n"); + printf(" -n Disable output storage\n"); + printf(" -E Separate stderr to .e file\n"); + printf(" -O Set log filename (no path)\n"); + printf(" -z Gzip output (unless -n)\n"); + printf(" -f Run in foreground\n"); + printf(" -m Email results via ssmtp\n"); + printf(" -d Schedule execution after last job\n"); + printf(" -D Schedule execution after specified IDs\n"); + printf(" -W Schedule after successful IDs (exit 0)\n"); + printf(" -L [label] Assign job label\n"); + printf(" -N [num] Required slots (default: 1)\n"); } static void print_version() { puts(version); } @@ -867,7 +812,7 @@ int main(int argc, char **argv) { if (cont_uid != 0) { printf("To resume user ID: %d\n", cont_uid); } else { - printf("To rResume all users by `Root`\n"); + printf("To resume all users from `Root`\n"); } c_resume_user(cont_uid); c_wait_server_lines(); diff --git a/notifications-sound.wav b/notifications-sound.wav index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ad7296faa856eec960e5906b6db9145b91376bd5 100644 GIT binary patch literal 242756 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

FC#m0yorVEEYuyr`lPE6W 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≺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

;Fo9TIpOX^=qa3xZ1U^qn$UB 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 literal 0 HcmV?d00001 diff --git a/server.c b/server.c index c076187..592c3c9 100644 --- a/server.c +++ b/server.c @@ -24,6 +24,7 @@ #include #include #include +#include #ifdef HAVE_UCRED_H #include @@ -83,6 +84,9 @@ static void s_send_version(int s) { send_msg(s, &m); } + + + static void sigterm_handler(int n) { const char *dumpfilename; int fd; @@ -292,8 +296,6 @@ void server_main(int notify_fd, char *_path) { server_loop(ls); } - - static int get_conn_of_jobid(int jobid) { int i; for (i = 0; i < nconnections; ++i) @@ -490,6 +492,15 @@ static enum Break client_read(int index) { // printf("client_read(%d), m.type = %d\n", index, m.type); int ts_UID = client_cs[index].ts_UID; + // Time-out unlock + if (user_locker > 0) { // locked by no-root user + time_t dt = time(NULL) - locker_time; + if (dt > DEFAULT_USER_LOCK_TIME) { + user_locker = -1; + } + } + + /* Process message */ switch (m.type) { case REFRESH_USERS: diff --git a/version.h b/version.h index c382ac6..773c942 100644 --- a/version.h +++ b/version.h @@ -6,7 +6,7 @@ #define TASK_SPOOLER_VERSION_H #ifndef TS_VERSION -#define TS_VERSION 2.1.0a +#define TS_VERSION 2.1.1a #endif /* from https://github.com/LLNL/lbann/issues/117 From 64ed18e93855d9c6978c04e3c81d4e258908e26f Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Sun, 23 Mar 2025 23:18:55 +0800 Subject: [PATCH 90/91] fixed some bugs --- Makefile | 2 +- README.md | 2 +- default.inc | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index c2ee973..0ba5e7d 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ PREFIX?=/usr/local PREFIX_LOCAL=~ GLIBCFLAGS=#-D_XOPEN_SOURCE=500 -D__STRICT_ANSI__ CPPFLAGS+=$(GLIBCFLAGS) -CFLAGS?=-pedantic -ansi -Wall -g -std=gnu11 -DNO_TASKSET -DSOUND -fcommon -Wno-format-truncation +CFLAGS?=-pedantic -ansi -Wall -g -std=gnu11 -fcommon -Wno-format-truncation # -DTASKSET -DSOUND OBJECTS=main.o \ server.o \ server_start.o \ diff --git a/README.md b/README.md index ecb2fa9..fab890e 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Simple run the provided script ``` ./make ``` -if you don't need the **taskset** processors binding feature, try to add`-DNO_TASKSET` option of `CFLAGS`. +if you need the **taskset** processors binding feature, try to add`-DTASKSET` option of `CFLAGS`. **The default positions** of log file and database is defined in `default.inc`. diff --git a/default.inc b/default.inc index cc727ed..317fc72 100644 --- a/default.inc +++ b/default.inc @@ -1,14 +1,14 @@ -#define DEFAULT_USER_PATH "/home/kylin/task-spooler/user.txt" -#define DEFAULT_LOG_PATH "/home/kylin/task-spooler/log.txt" -#define DEFAULT_SQLITE_PATH "/home/kylin/task-spooler/task-spooler.db" -#define DEFAULT_EMAIL_SENDER "kylincaster@foxmail.com" +#define DEFAULT_USER_PATH "/home/kylin/Public/task-spooler/user.txt" +#define DEFAULT_LOG_PATH "/home/kylin/Public/task-spooler/log.txt" +#define DEFAULT_SQLITE_PATH "/home/kylin/Public/task-spooler/task-spooler.db" +#define DEFAULT_EMAIL_SENDER "XXX@foxmail.com" #define DEFAULT_EMAIL_TIME 45.0 -#define DEFAULT_USER_LOCK_TIME 5 +#define DEFAULT_USER_LOCK_TIME 30 #define DEFAULT_HPC_NAME "intel_laptop" enum { MAXCONN = 1000 }; enum { DEFAULT_MAXFINISHED = 1000 }; -#define DEFAULT_NOTIFICATION_SOUND "/home/kylin/task-spooler/notifications-sound.wav" -#define DEFAULT_ERROR_SOUND "/home/kylin/task-spooler/error.wav" +#define DEFAULT_NOTIFICATION_SOUND "/home/kylin/Public/task-spooler/notifications-sound.wav" +#define DEFAULT_ERROR_SOUND "/home/kylin/Public/task-spooler/error.wav" #define DEFAULT_PULSE_SERVER "unix:/mnt/wslg/PulseServer" \ No newline at end of file From e1e178eb6e338a55852615fbacc2ff3837af83bf Mon Sep 17 00:00:00 2001 From: kylincaster <12872755+kylincaster@user.noreply.gitee.com> Date: Thu, 27 Mar 2025 18:58:06 +0800 Subject: [PATCH 91/91] add the time-out for root --- README.md | 3 ++- default.inc | 1 + list.c | 9 ++++++--- server.c | 4 +++- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fab890e..7032bb8 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,8 @@ if you need the **taskset** processors binding feature, try to add`-DTASKSET` op #define DEFAULT_SQLITE_PATH "/home/kylin/task-spooler/task-spooler.db" #define DEFAULT_EMAIL_SENDER "kylincaster@foxmail.com" #define DEFAULT_EMAIL_TIME 45.0 -#define DEFAULT_USER_LOCK_TIME 5 +#define DEFAULT_USER_LOCK_TIME 30 +#define DEFAULT_ROOT_LOCK_TIME 86400 #define DEFAULT_HPC_NAME "intel_laptop" enum { MAXCONN = 1000 }; diff --git a/default.inc b/default.inc index 317fc72..e03e47c 100644 --- a/default.inc +++ b/default.inc @@ -4,6 +4,7 @@ #define DEFAULT_EMAIL_SENDER "XXX@foxmail.com" #define DEFAULT_EMAIL_TIME 45.0 #define DEFAULT_USER_LOCK_TIME 30 +#define DEFAULT_ROOT_LOCK_TIME 86400 #define DEFAULT_HPC_NAME "intel_laptop" enum { MAXCONN = 1000 }; diff --git a/list.c b/list.c index e36acbd..1debbda 100644 --- a/list.c +++ b/list.c @@ -79,8 +79,11 @@ char *joblist_headers() { char extra[100] = ""; if (user_locker != -1) { time_t dt = time(NULL) - locker_time; - snprintf(extra, 100, "Locked by `%s` for %ld sec.", - user_name[user_locker], dt); + float real_ms = (float)(dt); + const char *unit = time_rep(&real_ms); + + snprintf(extra, 100, "Locked by `%s` for %3.2f %s.", + user_name[user_locker], real_ms, unit); } line = malloc(256); @@ -326,7 +329,7 @@ static char *plainprint_noresult(const struct Job *p) { error("Malloc for %i failed.\n", maxlen); float real_ms = 0; - const char* unit = "sx"; + const char* unit = "sec"; if (p->state == RUNNING) { struct timeval starttv = p->info.start_time; struct timeval endtv; diff --git a/server.c b/server.c index 592c3c9..67e9a8b 100644 --- a/server.c +++ b/server.c @@ -495,7 +495,9 @@ static enum Break client_read(int index) { // Time-out unlock if (user_locker > 0) { // locked by no-root user time_t dt = time(NULL) - locker_time; - if (dt > DEFAULT_USER_LOCK_TIME) { + if (user_locker == 0 && dt > DEFAULT_ROOT_LOCK_TIME) { + user_locker = -1; + } else if (dt > DEFAULT_USER_LOCK_TIME) { user_locker = -1; } }