diff --git a/Makefile b/Makefile index 852e5ca..0ba5e7d 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ + 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=c11 +CFLAGS?=-pedantic -ansi -Wall -g -std=gnu11 -fcommon -Wno-format-truncation # -DTASKSET -DSOUND OBJECTS=main.o \ server.o \ server_start.o \ @@ -18,7 +19,11 @@ OBJECTS=main.o \ print.o \ info.o \ env.o \ - tail.o + tail.o \ + user.o \ + cJSON.o \ + sqlite.o \ + taskset.o TARGET=ts INSTALL=install -c @@ -27,7 +32,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 $@ @@ -36,8 +41,9 @@ $(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 server.o: server.c main.h client.o: client.c main.h @@ -50,9 +56,14 @@ 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) + rm -f *.o $(TARGET); killall ts; rm ts; install: $(TARGET) $(INSTALL) -d $(PREFIX)/bin diff --git a/README.md b/README.md index 025ed00..7032bb8 100644 --- a/README.md +++ b/README.md @@ -1,108 +1,132 @@ -# Task Spooler +# Task Spooler PLUS -Originally, [Task Spooler by Lluís Batlle i Rossell](https://vicerveza.homeunix.net/~viric/soft/ts/). +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 in freshmeat.net: +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. -> 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. +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 See [CHANGELOG](CHANGELOG.md). -## Tutorial +## Features -A tutorial with colab is available [here](https://librecv.github.io/blog/spooler/task%20manager/deep%20learning/2021/02/09/task-spooler.html). +I have enhanced task-spooler to support task execution on my workstation with multiple users. Below are the key features of task-spooler-PLUS: -## Features +- **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 -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 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`. + +```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" +#define DEFAULT_EMAIL_SENDER "kylincaster@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 }; +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 "·" +``` -* 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. +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. -![ts-sample](assets/sample.png) -## Setup -### Install Task Spooler +In `taskset.c`, **the processor binding sequence** is determined by three variables: `MAX_CORE_NUM`,`MAX_CORE_NUM_HALF`, and `MAX_CORE_NUM_QUAD`. -Simple run the provided script +​ `MAX_CORE_NUM` represents the total number of processors available on the system. + +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: ``` -./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 `man`, you may also need to add `$HOME/.local/share/man` to `$MANPATH`. + + +To use `ts` anywhere, `ts` needs to be added to `$PATH` if it hasn't been done already. #### 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 config file is shown as an example. The UID could be found by `id -u [user]`. ``` -./uninstall +# 1231 # comments +TS_SLOTS = 4 # The number of Total 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? - -## 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 @@ -120,74 +144,127 @@ 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. ``` -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_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. - 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 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. - --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. + --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: - -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 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 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: - -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 sendmail). - -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). + -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) ``` -## Thanks + +## Restore from a fatal crush +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 +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 +``` +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. @@ -195,6 +272,7 @@ Options adding jobs: * 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/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 cf07ec7..c698a04 100644 --- a/client.c +++ b/client.c @@ -4,16 +4,17 @@ Please find the license in the provided COPYING file. */ +#include #include -#include -#include #include -#include +#include #include #include -#include +#include +#include #include "main.h" +extern int client_uid; static void c_end_of_job(const struct Result *res); @@ -22,860 +23,1045 @@ 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 charArray_string(command_line.command.num, command_line.command.array); +} - return commandstring; +char *charArray_string(int num, char** array) { + int size; + int i; + char *commandstring; + + size = 0; + + /* 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; - - m.type = NEWJOB; - - new_command = build_command_string(); - - 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; - - /* 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 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 environment */ - send_bytes(server_socket, myenv, m.u.newjob.env_size); + // printf("new _job \n"); + struct Msg m = default_msg(); + char *new_command, path[2048]; + char *myenv; + + m.type = NEWJOB; + + getcwd(path, 2048); + 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 + 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; + 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; + m.u.newjob.start_time = command_line.start_time; + m.u.newjob.taskset_flag = command_line.taskset_flag; + + + + /* 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 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); + + /* 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); + + // free(new_command); + free(myenv); +} - free(new_command); - 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; + 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 or conflict jobid\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; +} +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_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"); + error("Error in wait_server_commands"); - return m.u.jobid; + 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(m.jobid, &result); + } + c_end_of_job(&result); + return result.errorlevel; + } + } + return -1; } -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; - } - } - 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); - } +// 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; + int error_sig = 0; + 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)) + // printf("Error in wait_server_lines 2"); + // break; + 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); + 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(); + struct Msg m = default_msg(); + + m.type = 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); +} + +void c_list_jobs_all() { + 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_ALL; + 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); } /* 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); + + /* 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); + + 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"); +} + +void c_show_info() { + 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 = INFO; + m.jobid = command_line.jobid; - /* Set up a 2 second timeout to receive the - version msg. */ + send_msg(server_socket, &m); + while (1) { 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("Error in wait_server_lines"); - error("Wrong server version. Received %i, expecting %i", - m.u.version, PROTOCOL_VERSION); + 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); } + } +} - /* 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_refresh_user() { + struct Msg m = default_msg(); + m.type = REFRESH_USERS; + send_msg(server_socket, &m); } -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); - } - } +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_show_last_id() { - struct Msg m = default_msg(); - int res; +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; +} - m.type = LAST_ID; - send_msg(server_socket, &m); +void c_suspend_user(int uid) { + struct Msg m = default_msg(); + // int res; + m.type = SUSPEND_USER; + m.jobid = uid; + send_msg(server_socket, &m); +} - /* Receive the answer */ - res = recv_msg(server_socket, &m); - if (res != sizeof(m)) - error("Error in get_output_file"); +void c_resume_user(int uid) { + struct Msg m = default_msg(); + // int res; + m.type = RESUME_USER; + m.jobid = uid; + send_msg(server_socket, &m); +} - 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_show_last_id() { + struct Msg m = default_msg(); + int res; + + 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"); + + switch (m.type) { + case LAST_ID: + printf("%d\n", m.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(); - - /* 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 the filename */ - if (command_line.store_output) - send_bytes(server_socket, ofname, m.u.output.ofilename_size); + 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; + + send_msg(server_socket, &m); + + /* 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(); - m.type = KILL_SERVER; - send_msg(server_socket, &m); + struct Msg m = default_msg(); + if (client_uid != 0) { + printf("Only the root can shutdown the ts server\n"); + return; + } + char buf[10]; + printf("Do you want to kill the task-spooler server? (Yes/no) "); + scanf("%3s", buf); + if (strcmp(buf, "Yes") != 0) { + return; + } else { + printf("Kill the server!\n"); + } + + m.type = KILL_SERVER; + send_msg(server_socket, &m); } void c_clear_finished() { - struct Msg m = default_msg(); + struct Msg m = default_msg(); + if (client_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; - m.type = CLEAR_FINISHED; - send_msg(server_socket, &m); + 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.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; +} + +void c_hold_job(int jobid) { + /* This will exit if there is any error */ + /* + int pid = 0; + get_output_file(&pid); + + if (pid == -1 || pid == 0) { + 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); + + struct Msg m = default_msg(); + m.type = HOLD_JOB; + m.jobid = jobid; + send_msg(server_socket, &m); + c_wait_server_lines(); +} + +void c_cont_job(int jobid) { + /* + int pid = 0; + 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); + + struct Msg m = default_msg(); + m.type = CONT_JOB; + m.jobid = jobid; + send_msg(server_socket, &m); + // not error, restart job + if (wait_server_lines_and_check("Error") == 0) { + c_wait_server_lines(); + } + } + 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(); - - return tail_file(str, -1 /* All the lines */); + 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 */); } 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); - - 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); + 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, 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; + char buf[10]; + 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; + } else { + printf("Kill all jobs!\n"); + } + /* 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"); + struct Msg m = default_msg(); + int res; + char *string = 0; + + /* Send the request */ + m.type = REMOVEJOB; + m.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); + if (strncmp(string, "Running job", 11) == 0) { + printf("%s", string); + c_kill_job(); + } else { + fprintf(stderr, "Error in the request: %s", string); } - /* This will never be reached */ + 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.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.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(); - - /* Send the request */ - m.type = SET_MAX_SLOTS; - m.u.max_slots = command_line.max_slots; - send_msg(server_socket, &m); + 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); } 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("Max slots: %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.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.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.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.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"); - } - - return string; - default: - warning("Wrong internal message in get_logdir"); +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; } 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; - - /* 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); + 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"); - /* 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/default.inc b/default.inc new file mode 100644 index 0000000..e03e47c --- /dev/null +++ b/default.inc @@ -0,0 +1,15 @@ +#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 30 +#define DEFAULT_ROOT_LOCK_TIME 86400 +#define DEFAULT_HPC_NAME "intel_laptop" + +enum { MAXCONN = 1000 }; +enum { DEFAULT_MAXFINISHED = 1000 }; + +#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 diff --git a/error.c b/error.c index 7911e21..8328c8e 100644 --- a/error.c +++ b/error.c @@ -149,12 +149,13 @@ 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(); exit(-1); } @@ -166,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/error.wav b/error.wav new file mode 100644 index 0000000..57f5f83 Binary files /dev/null and b/error.wav differ diff --git a/execute.c b/execute.c index 7dd5f42..c12ad3f 100644 --- a/execute.c +++ b/execute.c @@ -4,280 +4,428 @@ 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 -#include #include "main.h" /* from signals.c */ extern int signals_child_pid; /* 0, not set. otherwise, set. */ +extern int client_uid; -/* 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"); +/* +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; } - 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; + + sprintf(path, "/proc/%i", pid); + int dir_fd = open(path, 0); + if (dir_fd < 0) { + close(in_fd); + return -1; } - command = build_command_string(); - if (command_line.send_output_by_mail) { - send_mail(command_line.jobid, result->errorlevel, ofname, command); + 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); } - 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); + + close(dir_fd); + 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; + 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; + 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); + if (client_uid == 0) { + status = ptrace_pid(pid); + /* + char buff[]; + 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; + } + + + + command = command_line.linux_cmd; // 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); + + /* 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); + + free(command); + free(ofname); +} +/* Returns errorlevel */ +static void run_parent(int fd_read_filename, int pid, struct Result *result) { + int status = 0; + 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(); + // printf("runjob_ok %s\n", ofname); + 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 = command_line.linux_cmd; // 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); + + /* 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); + + free(command); + free(ofname); } 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, int jobid) { + char *outfname; + char errfname[sizeof outfname + 2]; /* .e */ + char jobid_str[100]; + + int namesize; + 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; + } + snprintf(jobid_str, 100, "%d", jobid); + + // int len_outfname = 1 + strlen(label) + strlen(".XXXXXX") + 1; + int len_outfname = 3 + strlen(label) + strlen(jobid_str); + 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 */ + int lname; + char *outfname_full; + + // if (tmpdir == NULL) + // tmpdir = "/tmp"; + lname = strlen(outfname) + strlen(tmpdir) + 5 /* \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 */ + outfd = open(outfname_full, O_CREAT | O_WRONLY | O_TRUNC, 0644); + 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, 0644); + 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 */ + 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, 0644); + 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(); + // only execute the command without the relink flag + 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); +int run_job(int jobid, struct Result *res) { + int pid; + int errorlevel = 0; + int p[2]; + char path[2048]; + getcwd(path, 2048); + // const char *tmpdir = get_logdir(); + // printf("tmpdir: %s\n", tmpdir); + + /* For the parent */ + /*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); + + + pid = fork(); + + switch (pid) { + case 0: + restore_sigmask(); + close(server_socket); + close(p[0]); + 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"); + // 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/info.c b/info.c index 50c3977..cfb1beb 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 4058bc4..dbe220d 100644 --- a/jobs.c +++ b/jobs.c @@ -5,31 +5,40 @@ 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 +#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 + +char *email_sender; struct Notify { - int socket; - int jobid; - struct Notify *next; + int socket; + int jobid; + struct Notify *next; }; /* Globals */ -static struct Job *firstjob = 0; -static struct Job *first_finished_job = 0; -static int jobids = 0; +static struct Job firstjob = {0}; +static struct Job first_finished_job = {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 @@ -38,1540 +47,2708 @@ 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; 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); -static void destroy_job(struct Job* p) { +void s_set_jobids(int i) { + jobids = 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; + } + } +} + +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); + 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, + 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 + 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; + 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); free(p->command); + free(p->work_dir); free(p->output_filename); pinfo_free(&p->info); free(p->depend_on); free(p->label); +#ifdef TASKSET + free(p->cores); +#endif free(p); + } +} + +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; + p->num_allocated = 0; + // user_queue[ts_UID]--; + user_jobs[ts_UID]--; +#ifdef TASKSET + unlock_core_by_job(p); +#endif +} + +static int config_running(struct Job *p) { + if (p == NULL || (p->state != PAUSE && p->state != QUEUED)) return 1; + +#ifdef TASKSET + set_task_cores(p); +#endif + + 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; +} + +/* 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; } -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; +void s_sort_jobs() { + struct Job queue; + struct Job *p_queue, *p_run; + struct Job *p; - /* Show Queued or Running jobs */ - p = firstjob; - while (p != 0) { - if (p->next == final) - return p; - p = p->next; - } + p_run = &firstjob; + p_queue = &queue; - return 0; + 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 *findjob(int jobid) { - struct Job *p; - - /* Show Queued or Running jobs */ - p = firstjob; - while (p != 0) { - if (p->jobid == jobid) - return p; - p = p->next; +static struct Job *find_previous_job(const struct Job *final) { + struct Job *p; + + /* Show Queued or Running jobs */ + p = &firstjob; + while (p != NULL) { + if (p->next == final) + return p; + p = p->next; + } + + return NULL; +} + +struct Job *findjob(int jobid) { + struct Job *p; + /* Show Queued or Running jobs */ + p = firstjob.next; + while (p != 0) { + if (p->jobid == jobid) + return p; + p = p->next; + } + 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; + struct Job *p = &firstjob; + + while (p->next != NULL) { + p = p->next; + if (p->pid == pid) + return p; + } + return NULL; +} + +// 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)); + int res = kill(pid, 0); + // printf("res = %d\n", res); + return res == 0; +} + +// 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; + } - return 0; + char filename[256]; + struct stat t_stat; + + 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; + } + + int job_tsUID = get_tsUID(t_stat.st_uid); + 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]); + send_list_line(s, buff); + return -1; + } + return job_tsUID; } 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.next; + 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.next; + 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.next; + 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); + 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); + 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; + 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); +void s_kill_all_jobs(int s, int ts_UID) { - /* send running job PIDs */ - p = firstjob; - while (p != 0) { - if (p->state == RUNNING) - send(s, &p->pid, sizeof(int), 0); + struct Job *p; + s_count_running_jobs(s, ts_UID); - p = p->next; - } + /* send running job PIDs */ + p = firstjob.next; + 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; + } } -void s_count_running_jobs(int s) { - int count = 0; - struct Job *p; - struct Msg m = default_msg(); +void s_count_running_jobs(int s, int ts_UID) { + 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.next; + while (p != 0) { + if (p->state == RUNNING && (ts_UID == 0 || p->ts_UID == ts_UID)) + ++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); +} + +int s_get_job_tsUID(int jobid) { + struct Job *p = get_job(jobid); + if (p == NULL) { + return -1; + } else { + return p->ts_UID; + } } 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.next; + + 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.next; + 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) { + snprintf(buff, 255, "[get_label0] Job %i not finished or not running.\n", + jobid); + send_list_line(s, buff); + 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.next; + + 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.next; + 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) { + snprintf(buff, 255, "[get_label1] Job %i not finished or not running.\n", + jobid); + send_list_line(s, buff); + return; + } + cmd = (char *)malloc(strlen(p->command) + 1); + sprintf(cmd, "%s\n", p->command); + send_list_line(s, cmd); + 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); - p->state = RUNNING; + 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 = PAUSE; + return; + } else { + p->state = QUEUED; + } + } + 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 */ 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; -} - -void s_list(int s) { - struct Job *p; - char *buffer; - + 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; + case RELINK: + jobstate = "relink "; + break; + case WAIT: + jobstate = "wait "; + break; + case DELINK: + jobstate = "delink "; + break; + case LOCKED: + jobstate = "locked "; + break; + case PAUSE: + jobstate = "holdon "; + break; + default: + jobstate = "UNKNOWN "; + } + return jobstate; +} + +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); /* 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 = 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; + } + 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) { + while (p != NULL) { + if (p->ts_UID == ts_UID || ts_UID == 0) { buffer = joblist_line(p); send_list_line(s, buffer); free(buffer); - p = p->next; + } + 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; } -} - -void s_list_plain(int s) { - struct Job *p; - char *buffer; - /* Show Queued or Running jobs */ - p = firstjob; + /* Serialize Finished jobs */ + p = first_finished_job.next; while (p != 0) { - if (p->state != HOLDING_CLIENT) { - buffer = joblist_line_plain(p); - send_list_line(s, buffer); - free(buffer); - } - p = p->next; + int success = add_job_to_json_array(p, jobs); + if (success == 0) { + goto end; + } + p = p->next; } - p = first_finished_job; + buffer = cJSON_PrintUnformatted(jobs); + if (buffer == NULL) { + error("Error converting jobs to JSON."); + goto end; + } - /* Show Finished jobs */ + // 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 = 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 } -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; +void s_list_all(int s, enum ListFormat listFormat) { + 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.next; + while (p != 0) { + if (p->state != HOLDING_CLIENT) { + buffer = joblist_line(p); + send_list_line(s, buffer); + free(buffer); } + p = p->next; + } - p = firstjob; - while (p->next != 0) - p = p->next; + p = first_finished_job.next; + if (p != NULL && firstjob.next != NULL) + send_list_line(s, "\n ----- Finished -----\n"); + + /* 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.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->next = (struct Job *) malloc(sizeof(*p)); - p->next->next = 0; - p->next->output_filename = 0; - p->next->command = 0; + p = first_finished_job.next; - return 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; + + p = &firstjob; + while (p->next != 0) + 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; + p->next->pid = 0; + 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; + p->result.errorlevel = 0; + #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; + info->end_time.tv_sec = 0; + 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; } /* 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.next; + 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.next; + 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; +int s_newjob(int s, struct Msg *m, int ts_UID) { + s_update_slots_usage(); + + struct Job *p = NULL; + int res; + // 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; + } else if (p->state == WAIT) { + // jobDB_wait_num--; + ; // waitjob_flag = 1; + } else if (p->state == LOCKED) { + // jobDB_wait_num--; + ; // waitjob_flag = 1; + } else { + return -1; + } + } + } + if (p == NULL) { 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; - 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++; + // 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 + p->ts_UID = ts_UID; // get_tsUID(m->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; + 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; + 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, + * 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; + /* if dependency list is empty after removing invalid dependencies, make it + * independent */ + if (p->depend_on_size == 0) + p->depend_on = 0; + if (p->state != DELINK && p->state != WAIT && p->state != LOCKED) { 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); + } + + /* load the command */ + + 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, buff, m->u.newjob.command_size); + if (res == -1) + error("wrong bytes received"); + + 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) { + 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"); - - /* 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->work_dir = ptr; + } + + /* load the label */ + p->label = NULL; + 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; + } + + 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; + 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); + } + + 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; + 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; } /* This assumes the jobid exists */ -void s_removejob(int jobid) { - struct Job *p; - struct Job *newnext; - - if (firstjob->jobid == jobid) { - struct Job *newfirst; - - /* 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); - - newnext = p->next->next; - - destroy_job(p->next); - p->next = newnext; +void s_delete_job(int jobid) { + struct Job *p; + struct Job *newnext; + /* + if (firstjob.next.jobid == jobid) { + struct Job *newfirst; + + // 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); + + newnext = p->next->next; + + destroy_job(p->next); + p->next = newnext; } /* -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; + struct Job *p; - const int free_slots = max_slots - busy_slots; + /* 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; - /* 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; + const int free_slots = max_slots - busy_slots; - /* If there are no jobs to run... */ - if (firstjob == 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; - /* Look for a runnable task */ - p = firstjob; + /* Look for a runnable task */ + for (int i = 0; i < user_number; i++) { + uid = (uid + 1) % user_number; + if (user_queue[uid] == 0) { + continue; + } + p = firstjob.next; 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 (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; - } + 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_queue[id]--; + return p->jobid; } - p = p->next; + } + p = p->next; } - - return -1; + } + 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 DEFAULT_MAXFINISHED; + int num = abs(atoi(limit)); + if (num < 1) + num = DEFAULT_MAXFINISHED; + return num; } /* 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 = 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; - } + max = get_max_finished_jobs(); + 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.next; + first_finished_job.next = tmp->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; + int err = insert_DB(j, "Finished"); + if (err == 0) { + delete_DB(j->jobid, "Jobs"); + } + +#ifdef TASKSET + 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) { - 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; } +/* 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); + 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); + struct Job *p = findjob(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; + if (p == NULL) + error("on jobid %i finished, it doesn't exist", jobid); - /* 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); + /* 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->num_allocated != 0) { + free_cores(p); + } - 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; - } - } + /* Mark state */ + if (result->skipped) + p->state = SKIPPED; + else + p->state = FINISHED; - /* Add it to the finished queue (maybe temporarily) */ - if (p->should_keep_finished || in_notify_list(p->jobid)) - new_finished_job(p); + 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; + } + + 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 = &firstjob; + struct Job *newfirst = p->next; + + while (jpointer->next != p) { + jpointer = jpointer->next; + } + + /* 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->next = newfirst; + } +} + +static int fork_cmd(const 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; } -void s_clear_finished() { - struct Job *p; +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); - if (first_finished_job == 0) - return; + j->state = DELINK; - p = first_finished_job; - first_finished_job = 0; + // jobDB_Jobs[jobDB_num] = j; + // jobDB_num++; + (*p)->next = j; + (*p) = j; - while (p != 0) { - struct Job *tmp; - tmp = p->next; - destroy_job(p); - p = tmp; + char c[64]; + sprintf(c, " --relink %d -J %d ", j->pid, j->jobid); + 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; + j = NULL; + } else { + delete_DB(j->jobid, "Jobs"); + } + } else if (j->state == QUEUED || j->state == LOCKED) { + printf("add the queue job %d\n", j->jobid); + 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_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; + j = NULL; + free(str); + /* + 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; + if (first_finished_job.next == NULL) + return; + + p = first_finished_job.next; + other_user_job->next = NULL; + while (p != NULL) { + 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; + other_user_job = p; } + p = tmp; + } + other_user_job->next = 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_pids(p->pid, SIGSTOP, NULL); + } + } + p = p->next; + } } +// run the jobs 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; + + struct Job *p; + p = findjob(jobid); + if (p == 0) + error("Job %i already run not found on runjob_ok", jobid); + 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; + if (oname != NULL && strlen(oname) != 0) { p->output_filename = oname; - pinfo_set_start_time(&p->info); + } + pinfo_set_start_time_check(&p->info); + if (pid > 0) { + // printf("s_process_runjob_ok = %d\n", jobid); + write_logfile(p); + //if (p->state == PAUSE) { + // config_running(p); + //} + insert_or_replace_DB(p, "Jobs"); + } + } 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; + m.jobid = jobid; + 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.next; + if (p == 0) + error("Internal state WAITING, but job not run." + "firstjob = %x", + firstjob.next); } 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.next; + if (p == 0) { + send_list_line(s, "No jobs.\n"); return; + } + while (p->next != 0) + p = p->next; } + } else { + p = firstjob.next; + 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.next; + while (p != 0 && p->jobid != jobid) + p = p->next; } + } + + if (p == 0) { + snprintf(buff, 255, "[s_send_runjob] Job %i not finished or not running.\n", + jobid); + send_list_line(s, buff); + return; + } + + m.type = INFO_DATA; + + float t; + 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, "]&& "); + } + 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%s\n", jstate2string(p->state), p->pid, status); + +#ifdef TASKSET + if (p->cores != NULL) { + int buffer_len = strlen(p->cores) + 100; + 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 + 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, "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)); + 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) { + t = pinfo_time_run(&p->info); + 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 (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"); } 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.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; - } - } else { - p = get_job(jobid); - if (p != 0 && p->state != RUNNING - && p->state != FINISHED - && p->state != SKIPPED) - p = 0; - } +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(); +} - 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; +void s_suspend_user_all(int s) { + s_update_slots_usage(); + for (int i = 1; i < user_number; i++) { + s_suspend_user(s, i); + } +} + +void s_resume_user_all(int s) { + for (int i = 1; i < user_number; i++) { + s_resume_user(s, i); + } +} + +void s_resume_user(int s, int ts_UID) { + // get the sequence of ts_UID + if (ts_UID < 0 || ts_UID > USER_MAX) + return; + + 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->ts_UID == ts_UID && p->state == PAUSE) { + // p->state = HOLDING_CLIENT; + if (p->pid != 0) { + // printf("pid = %d\n", p->pid); + config_running(p); + } } + p = p->next; + } + snprintf(buff, 255, "Resume user: [%04d] %-20s\n", user_UID[ts_UID], + user_name[ts_UID]); + send_list_line(s, buff); +} + +void s_suspend_user(int s, int ts_UID) { + // get the sequence of ts_UID + if (ts_UID < 0 || ts_UID > USER_MAX) + return; + + 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->ts_UID == ts_UID && p->state == RUNNING) { + // p->state = HOLDING_CLIENT; + if (p->pid != 0) { + safe_pause_pid(p); + p->state = PAUSE; + } else { + char *label = "(...)"; + if (p->label != NULL) + label = p->label; + snprintf(buff, 255, "Error in stop %s [%d] %s | %s\n", + user_name[ts_UID], p->jobid, label, p->command); + send_list_line(s, buff); + } + } + p = p->next; + } - if (p->state == SKIPPED) { - char tmp[50]; - if (jobid == -1) - sprintf(tmp, "The last job was skipped due to a dependency.\n"); + snprintf(buff, 255, "Suspend user: [%04d] %-20s\n", user_UID[ts_UID], + user_name[ts_UID]); + send_list_line(s, buff); +} - else - sprintf(tmp, "Job %i was skipped due to a dependency.\n", jobid); - send_list_line(s, tmp); +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.next; + if (p == 0) + error("Internal state WAITING, but job not run." + "firstjob = %x", + firstjob.next); + } else { + p = first_finished_job.next; + 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) { + 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); + send_list_line(s, buff); + return; + } + + if (p->state == SKIPPED) { + if (jobid == -1) + snprintf(buff, 255, "The last job was skipped due to a dependency.\n"); - 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); + snprintf(buff, 255, "Job %i was skipped due to a dependency.\n", jobid); + send_list_line(s, buff); + 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_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; + 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.next; + before_p = &first_finished_job; + if (p) { + while (p->next != 0) { + 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; - } + } + } + } else { + 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.next; + before_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 && client_tsUID == 0) { + client_tsUID = p->ts_UID; + } + + if (p == NULL || (p->ts_UID != client_tsUID)) { - /* Return the jobid found */ - *jobid = p->jobid; + if (*jobid == -1) + snprintf(buff, 255, "The last job cannot be removed.\n"); + 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", *jobid, + user_name[p->ts_UID], user_name[client_tsUID]); + } + } + send_list_line(s, buff); + return 0; + } + + if (p->state == RUNNING) { + if (p->pid != 0 && (p->ts_UID == 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]); + send_list_line(s, buff); + return 0; + } + send_list_line(s, "RUNNING\n"); + return 0; + } - /* Tricks for the check_notify_list */ + /* + 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; + delete_DB(p->jobid, "Jobs"); + /* Tricks for the check_notify_list */ + p->state = FINISHED; + p->result.errorlevel = -1; + notify_errorlevel(p); - /* Notify the clients in wait_job */ - check_notify_list(m.u.jobid); + /* Notify the clients in wait_job */ + check_notify_list(m.jobid); - /* Update the list pointers */ - if (p == first_finished_job) - first_finished_job = p->next; - else - before_p->next = p->next; + /* Update the list pointers */ + before_p->next = p->next; - destroy_job(p); + destroy_job(p); - m.type = REMOVEJOB_OK; - send_msg(s, &m); - return 1; + 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; +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 { + if (user_locker == ts_UID) { + res = 0; + } else { + res = 1; + } + } + return res; +} + +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"); + } else { + if (user_locker == -1) { + 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 == 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]); + } else { + snprintf(buff, 255, + "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); +} - /* Remove the notification */ - previous = first_notify; - if (n == previous) { - first_notify = n->next; - free(n); - return; +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 == 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 locked by other user cannot " + "be unlocked by [%d] `%s`\n", + user_UID[ts_UID], user_name[ts_UID]); + } } + } + send_list_line(s, buff); +} - /* if not the first... */ - while (previous->next != n) - previous = previous->next; +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); + } +} - previous->next = n->next; - free(n); +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); + } } -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); +static int safe_pause_pid(struct Job *p) { + kill(p->pid, SIGSTOP); + kill_pids(p->pid, SIGSTOP, NULL); + if (is_sleep(p->pid) == 1) { + free_cores(p); + return 0; + } else { + kill_pids(p->pid, SIGCONT, NULL); + 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]); + send_list_line(s, buff); + return; + } + 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 (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 (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 (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); + } + } else { + snprintf(buff, 255, "Error: cannot pause job [%d]\n", jobid); + } + send_list_line(s, buff); +} + +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; + } + 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 (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 (p->state == RUNNING) { + if (is_sleep(p->pid) == 0) { + snprintf(buff, 255, "job [%d] is aleady in RUNNING.\n", jobid); + } else { + kill_pids(p->pid, SIGCONT, NULL); + snprintf(buff, 255, "job [%d] is continued.\n", jobid); + } + } else { + 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 (config_running(p)) { + printf("Cannot set Job %i as RUNNING", p->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 rerun job [%d]\n", jobid); } + } // p->pid + + send_list_line(s, buff); +} +/* 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; + } - destroy_job(j); + /* if not the first... */ + while (previous->next != n) + previous = previous->next; + + previous->next = n->next; + free(n); +} + +static void destroy_finished_job(struct Job *j) { + 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; + } + } + error("Cannot destroy the expected job %i", j->jobid); } /* 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; - - /* Look in finished jobs if needed */ - if (p == 0) { - p = first_finished_job; - while (p != 0 && p->jobid != jobid) - p = p->next; - } + struct Job *p = 0; + + if (jobid == -1) { + /* Find the last job added */ + p = firstjob.next; + + if (p != 0) + while (p->next != 0) + p = p->next; + + /* Look in finished jobs if needed */ + if (p == 0) { + p = first_finished_job.next; + if (p != 0) + while (p->next != 0) + p = p->next; } + } else { + p = firstjob.next; + 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.next; + 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) { + if (jobid == -1) + snprintf(buff, 255, "The last job cannot be waited.\n"); + else + snprintf(buff, 255, "The job %i cannot be waited.\n", jobid); + send_list_line(s, buff); + 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.next; + if (p == 0) + error("Internal state WAITING, but job not run." + "firstjob = %x", + firstjob.next); } 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.next; + if (p == 0) { + send_list_line(s, "No jobs.\n"); + return; + } + while (p->next != 0) + p = p->next; } + } else { + p = firstjob.next; + 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.next; + 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) { + if (jobid == -1) + snprintf(buff, 255, "The last job cannot be waited.\n"); + else + snprintf(buff, 255, "The job %i cannot be waited.\n", jobid); + send_list_line(s, buff); + 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); +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); + 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) { - 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); } +/* move jobid upto the top of list */ 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 (jobid == -1) { + /* Find the last job added */ + p = firstjob.next; - 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); + if (p != 0) + while (p->next != 0) + p = p->next; + } else { + p = firstjob.next; + while (p != 0 && p->jobid != jobid) + p = p->next; + } + + // firstjob.next means no run job + if (p == 0 || firstjob.next == 0) { + if (jobid == -1) + snprintf(buff, 255, "The last job cannot be urged.\n"); + else + snprintf(buff, 255, "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, 255, "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; + movetop_DB(jobid); + 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; - - 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; - } - - /* 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); + struct Job *p1, *p2; + struct Job *prev1, *prev2; + struct Job *tmp; + + p1 = findjob(jobid1); + p2 = findjob(jobid2); + + if (p1 == NULL || p2 == NULL) { + snprintf(buff, 255, "The jobs %i and %i cannot be swapped.\n", jobid1, + jobid2); + send_list_line(s, buff); + 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; + swap_DB(jobid1, jobid2); + 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.next; + 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.next; + 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) { + if (jobid == -1) + snprintf(buff, 255, "The last job cannot be stated.\n"); + else + snprintf(buff, 255, "The job %i cannot be stated.\n", jobid); + send_list_line(s, buff); + 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.next; + 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.next; + 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.next; + 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.next; + 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 *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); + 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); + 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"); + 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 name */ + char *name = strtok(var, "="); - /* get the var value */ - char *val = strtok(NULL, "="); - setenv(name, val, 1); - free(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/kill_ppid.sh b/kill_ppid.sh new file mode 100755 index 0000000..87532b3 --- /dev/null +++ b/kill_ppid.sh @@ -0,0 +1,66 @@ +#!/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 + + + diff --git a/list.c b/list.c index 063a3e8..1debbda 100644 --- a/list.c +++ b/list.c @@ -4,409 +4,462 @@ Please find the license in the provided COPYING file. */ + #include #include #include #include +#include + #include "main.h" +#include "user.h" /* From jobs.c */ extern int busy_slots; extern int max_slots; +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'; + FILE *fp = NULL; + snprintf(filename, 256, "/proc/%d/stat", pid); + + fp = fopen(filename, "r"); + if (fp == NULL) { + 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, "[is_sleep] Error: not enough (3) tokens\n"); + 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) - 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; + char extra[100] = ""; + if (user_locker != -1) { + time_t dt = time(NULL) - locker_time; + 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); + snprintf(line, 256, + "%-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; } -static int max(int a, int b) { - return a > b ? a : b; +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; - - 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; + 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 = + p->output_filename; // shorten(p->output_filename, 20); + } + } else + output_filename = "stdout"; + + return output_filename; } 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), "]&& "); + const char *jobstate; + const char *output_filename; + int maxlen; + char *line; + /* 20 chars should suffice for a string like "[int,int,..]&& " */ + char dependstr[1024] = "[]"; + int cmd_len; + jobstate = jstate2string(p->state); + + if (p->state == RUNNING) { + if (p->pid == 0) { + jobstate = "N/A"; + } else { + if (is_sleep(p->pid) == 1) { + jobstate = "sleep "; + } } - - 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); + } + 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]; + 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 */ + + 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), "]"); + } + + struct timeval starttv = p->info.start_time; + struct timeval endtv; + float real_ms; + const char *unit; + if (p->state == QUEUED || p->pid == 0) { + 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); + } + + 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 + p->command_strip, cmd_len); + if (p->label) { + char *label = shorten(p->label, 10); + 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); + free(cmd); + } else { + char *cmd = shorten(p->command + p->command_strip, cmd_len); + char *label = "(..)"; + 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); + } + return line; } 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), "]&& "); - } - - 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); + const char *jobstate; + int maxlen; + char *line; + const char *output_filename; + /* 20 chars should suffice for a string like "[int,int,..]&& " */ + 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); + } + const char *unit = time_rep(&real_ms); + int cmd_len; + + jobstate = jstate2string_result(p); + output_filename = ofilename_shown(p); + + 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 */ + + 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); + char *cmd = shorten(p->command + p->command_strip, cmd_len); + if (p->label) { + char *label = shorten(p->label, 10); + 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); + free(cmd); + } else { + char *cmd = shorten(p->command + p->command_strip, cmd_len); + char *label = "(..)"; + 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); + } + + 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[256] = "[]&&"; + + jobstate = jstate2string(p->state); + output_filename = ofilename_shown(p); + 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 */ + + char* label = "(..)"; + if (p->label) { + label = p->label; + } + maxlen += 3 + strlen(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); + + float real_ms = 0; + const char* unit = "sec"; + 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; } + 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), "]&& "); + const char *jobstate; + int maxlen; + char *line; + const char *output_filename; + /* 20 chars should suffice for a string like "[int,int,..]&& " */ + char dependstr[256] = "[]"; + 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); + } + + const char *unit = time_rep(&real_ms); + + jobstate = jstate2string_result(p); + output_filename = ofilename_shown(p); + + maxlen = 4 + 1 + 10 + 1 + 20 + 1 + 8 + 1 + 25 + 1 + strlen(p->command) + + 30 + strlen(user_name[p->ts_UID]); /* 30 is the margin for errors */ + + + char* label = "(..)"; + if (p->label) { + label = p->label; + } + maxlen += 3 + strlen(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); - 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; + line = (char *)malloc(maxlen); + if (line == NULL) + error("Malloc for %i failed.\n", maxlen); + + + 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; } 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, "%s\n", p->command); - return line; + return line; } -char *time_rep(float *t) { - 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 = "h"; - - if (time_in_sec > 24) { - time_in_sec /= 24; - unit = "d"; - } - } +const char *time_rep(float *t) { + float time_in_sec = *t; + char *unit = "s"; + if (time_in_sec > 250) { + time_in_sec /= 60; + unit = "m"; + + if (time_in_sec > 100) { + time_in_sec /= 60; + unit = "h"; + + if (time_in_sec > 50) { + time_in_sec /= 24; + unit = "d"; + } } - *t = time_in_sec; - return unit; + } + *t = time_in_sec; + return unit; } 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 6408b92..5643d58 100644 --- a/main.c +++ b/main.c @@ -4,24 +4,25 @@ 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 -#include #include +#include +#include #include -#include +#include +#include #include +#include +#include // time() +#include +#include "default.inc" #include "main.h" +#include "version.h" + +#include "user.h" +int client_uid; +const int MAX_LEN = 1024 * 10; extern char *optarg; extern int optind, opterr, optopt; @@ -30,668 +31,961 @@ 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]; 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); -#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); -#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\n", + ts_version, 2024); } 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.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.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; + command_line.wait_enqueuing = 1; + command_line.stderr_apart = 0; + command_line.num_slots = 1; + command_line.require_elevel = 0; + command_line.logfile = NULL; + command_line.taskpid = 0; + 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() { - 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; +} + +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; - - 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", 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}, + {"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}, + {"tmp", 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}, + {"no-bind", no_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); + int c; + int res; + int optionIdx = 0; + + /* 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); + + 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, "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; + } 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 = 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, "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, "suspend") == 0) { + command_line.request = c_SUSPEND_USER; + command_line.label = optarg; /* reuse this var */ + } 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; + 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, "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; + else { + check_relink(command_line.taskpid); + } + } 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; + 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 'X': + 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': + command_line.request = c_REMOVEJOB; + command_line.jobid = str2int(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 = str2int(optarg); + break; + case 'o': + command_line.request = c_SHOW_OUTPUT_FILE; + command_line.jobid = str2int(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.email = optarg; + break; + case 't': + command_line.request = c_TAIL; + command_line.jobid = str2int(optarg); + break; + case 'p': + command_line.request = c_SHOW_PID; + command_line.jobid = str2int(optarg); + break; + case 'i': + command_line.request = c_INFO; + 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 = str2int(optarg); + break; + case 'F': + command_line.request = c_SHOW_CMD; + command_line.jobid = str2int(optarg); + break; + case 'N': + command_line.num_slots = str2int(optarg); + 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); + 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; + command_line.jobid = str2int(optarg); + break; + case 'u': + command_line.request = c_URGENT; + command_line.jobid = str2int(optarg); + break; + case 's': + command_line.request = c_GET_STATE; + command_line.jobid = str2int(optarg); + break; + case 'S': + command_line.request = c_SET_MAX_SLOTS; + command_line.max_slots = str2int(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 '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': + 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; + case 'M': + command_line.request = c_LIST; + command_line.list_format = DEFAULT; + 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); + } + + 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); + // 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 (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); + 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); + + 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("\nusage: %s [action] [-ngfmdE] [-L ] [-D ] [cmd...]\n\n", cmd); + printf("Environment Variables:\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(" --stime [start_time] Set the relinked task by starting + // time (Unix epoch).\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 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); -} +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 + snprintf(getopt_env, 20, "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; } +// TODO add the benchmark for the performance. 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; + jobsort_flag = 0; + user_locker = -1; + 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.request == c_CHECK_DAEMON) { + c_check_daemon(); + } + + if (command_line.need_server) { + if (command_line.request == c_DAEMON) { + ensure_server_up(1); + } else { + ensure_server_up(0); } - - 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; + c_check_version(); + } + + switch (command_line.request) { + case 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"); } - if (command_line.need_server) { - close(server_socket); + break; + case c_DAEMON: + break; + case c_CHECK_DAEMON: + break; + case c_HOLD_JOB: + c_hold_job(command_line.jobid); + // c_wait_server_lines(); + break; + + case c_CONT_JOB: + c_cont_job(command_line.jobid); + break; + case c_LOCK_SERVER: + errorlevel = c_lock_server(); + break; + case c_UNLOCK_SERVER: + errorlevel = c_unlock_server(); + break; + case c_SUSPEND_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; + } } - free(command_line.depend_on); - - return errorlevel; + if (stop_uid != 0) { + printf("To stop user ID: %d\n", stop_uid); + } else { + printf("To stop all users by `Root`\n"); + } + c_suspend_user(stop_uid); + c_wait_server_lines(); + } break; + case c_RESUME_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; + } + } + if (cont_uid != 0) { + printf("To resume user ID: %d\n", cont_uid); + } else { + printf("To resume all users from `Root`\n"); + } + c_resume_user(cont_uid); + c_wait_server_lines(); + } break; + 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("New JobID: %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_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: + 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(); + 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..7a8c40e 100644 --- a/main.h +++ b/main.h @@ -4,224 +4,273 @@ Please find the license in the provided COPYING file. */ -enum { - CMD_LEN = 500, - PROTOCOL_VERSION = 730 +#include +#include + +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, + REFRESH_USERS, + HOLD_JOB, + CONT_JOB, + LOCK_SERVER, + UNLOCK_SERVER, + SUSPEND_USER, + RESUME_USER, + 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, + NEWJOB_PID_NOK, + COUNT_RUNNING, + GET_LABEL, + LAST_ID, + KILL_ALL, + GET_CMD, + GET_LOGDIR, + SET_LOGDIR, + GET_ENV, + SET_ENV, + UNSET_ENV +}; + +enum ListFormat { + DEFAULT, + JSON, + TAB }; 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_DAEMON, + c_CHECK_DAEMON, + c_REFRESH_USER, + c_SUSPEND_USER, + c_RESUME_USER, + c_LOCK_SERVER, + c_UNLOCK_SERVER, + c_HOLD_JOB, + c_CONT_JOB, + 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 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 *linux_cmd; + char *label; + 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 */ + long start_time; + enum ListFormat list_format; }; -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, + PAUSE, + FINISHED, + SKIPPED, + HOLDING_CLIENT, + RELINK, + WAIT, + DELINK, + LOCKED, + }; 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 jobid; + union { + struct { + int command_size; + int command_size_strip; + int path_size; + int store_output; + int should_keep_finished; + int label_size; + int email_size; + int env_size; + int depend_on_size; + int wait_enqueuing; + int num_slots; + int taskpid; + long start_time; + int taskset_flag; + } newjob; + struct { + int ofilename_size; + int store_output; + int pid; + } output; + 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 term_width; + enum ListFormat list_format; + } 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; + char *work_dir; + int command_strip; + enum Jobstate state; + struct Result result; /* Defined in msg.h */ + char *output_filename; + int store_output; + int pid; + int ts_UID; + int should_keep_finished; + int *depend_on; + int depend_on_size; + int *notify_errorlevel_to; + int notify_errorlevel_to_size; + int dependency_errorlevel; + int taskset_flag; + char *label; + char *email; + struct Procinfo info; + int num_slots; + int num_allocated; +#ifdef TASKSET + char* cores; +#endif }; enum ExitCodes { - EXITCODE_OK = 0, - EXITCODE_UNKNOWN_ERROR = -1, - EXITCODE_QUEUE_FULL = 2 + EXITCODE_OK = 0, + EXITCODE_UNKNOWN_ERROR = -1, + EXITCODE_QUEUE_FULL = 2, + EXITCODE_RELINK_FAILED = 3 }; - /* main.c */ struct Msg default_msg(); struct Result default_result(); - /* client.c */ void c_new_job(); @@ -287,7 +336,7 @@ void c_get_logdir(); void c_set_logdir(); -char* get_logdir(); +char *get_logdir(); void c_get_env(); @@ -296,13 +345,14 @@ void c_set_env(); void c_unset_env(); /* jobs.c */ -void s_list(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); -int s_newjob(int s, struct Msg *m); +int s_newjob(int s, struct Msg *m, int ts_UID); -void s_removejob(int jobid); +void s_delete_job(int jobid); void job_finished(const struct Result *result, int jobid); @@ -310,13 +360,13 @@ int next_run_job(); void s_mark_job_running(int jobid); -void s_clear_finished(); +void s_clear_finished(int ts_UID); 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); @@ -332,7 +382,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); @@ -348,7 +398,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); @@ -360,11 +410,11 @@ 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); -void s_set_logdir(const char*); +void s_set_logdir(const char *); void s_get_env(int s, int size); @@ -384,14 +434,14 @@ 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); 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); @@ -453,7 +503,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, ...); @@ -471,6 +521,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); @@ -485,3 +536,93 @@ char *get_environment(); /* tail.c */ int tail_file(const char *fname, int last_lines); + +/* user.c */ +static const int root_UID = 0; +const char *get_kill_sh_path(); +void read_user_file(const char *path); +int get_tsUID(int uid); +void c_refresh_user(); +const char *get_user_path(); +const char *set_server_logfile(); +void write_logfile(const struct Job *p); +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); +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); +void check_relink(int pid); +char *charArray_string(int num, char** array); + +/* locker */ +int user_locker; +time_t locker_time; +int jobsort_flag; +int is_sleep(int pid); +// int check_running_dead(int jobid); + +/* jobs.c */ +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_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); +void s_set_jobids(int i); +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 s_check_holdon(); +void free_pause_array(); +struct Job *findjob(int jobid); +void setup_ssmtp(); + +/* client.c */ +void c_list_jobs_all(); +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(); + +/* sqlite.c */ +const char *get_sqlite_path(); +int open_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); +int movetop_DB(int jobid); +int swap_DB(int, int); +int set_jobids_DB(int value); +int get_jobids_DB(); +int set_state_DB(int jobid, int state); +// 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_check(int pos, const char* input, const char* c); + +/* taskset.c */ +void init_taskset(); +int set_task_cores(struct Job* p); +void unlock_core_by_job(struct Job* p); 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/notifications-sound.wav b/notifications-sound.wav new file mode 100644 index 0000000..ad7296f Binary files /dev/null and b/notifications-sound.wav differ diff --git a/print.c b/print.c index 80bda3c..4336467 100644 --- a/print.c +++ b/print.c @@ -4,44 +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); + } + } + 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); + } + *size = count; + return result; +} + +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, s_arg); //将数字字符串连接到str后面 + strcat(str, input + pos); //将s剩余的字符串连接到str后面 + } + return str; +} \ No newline at end of file diff --git a/relink.py b/relink.py new file mode 100755 index 0000000..fa7a127 --- /dev/null +++ b/relink.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +import sys +import datetime +import psutil +import os + +logfile = "/home/kylin/task-spooler/log.txt" +ts_CMD="ts" +days_num = 30 +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] + 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, jobid; + + +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(" only restore tasks with {} days, start by".format(days_num), t_line) +tasks = [] +for l in lines[:]: + user, procs, pid, tag, CMD, t_time, jobid = parse(l) + if (psutil.pid_exists(pid)): + if (t_time > t_line): + + 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, jobid, procs, CMD]) + # tasks.append([tag, pid, procs, int(t_time.timestamp()), CMD]) + else: + 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 {} -J {} -N {} "{}"'.format(ts_CMD, *i[1:]) + # CMD = 'ts --pid {} -N {} --stime {:} "{}"'.format(*i[1:]) + else: + 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/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 625076d..67e9a8b 100644 --- a/server.c +++ b/server.c @@ -4,46 +4,50 @@ Please find the license in the provided COPYING file. */ -#include -#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 -#include "main.h" +#ifdef HAVE_UCRED_H +#include +#endif +#ifdef HAVE_SYS_UCRED_H +#include +#endif -enum { - MAXCONN = 1000 -}; +#include -enum Break { - BREAK, - NOBREAK, - CLOSE -}; +#include "main.h" +#include "user.h" -char* logdir; +#include "default.inc" +enum Break { BREAK, NOBREAK, CLOSE }; + +char *logdir; +extern int busy_slots; /* 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 +60,10 @@ 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; + int ts_UID; }; /* Globals */ @@ -71,498 +76,753 @@ 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(0, slots); + } } +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); - 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 */ + 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; +} - res = getrlimit(RLIMIT_NOFILE, &rlim); - if (res != 0) - warning("getrlimit for open files"); - else { - if (max > rlim.rlim_cur) - max = rlim.rlim_cur - MARGIN; +/* +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; } + } +} +*/ - if (max < 1) - error("Too few opened descriptors available"); - - return max; +/* +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; - - 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; - - path = _path; - - /* Move the server to the socket directory */ - dirpath = malloc(strlen(path) + 1); - strcpy(dirpath, path); - chdir(dirname(dirpath)); - free(dirpath); - - nconnections = 0; - - 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); - - res = bind(ls, (struct sockaddr *) &addr, sizeof(addr)); - if (res == -1) - error("Error binding."); - - res = listen(ls, 0); - if (res == -1) - error("Error listening."); - - install_sigterm_handler(); - - set_default_maxslots(); - - initialize_log_dir(); - + 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(); + /* Arbitrary limit, that will block the enqueuing, but should allow space + * for usual ts queries */ + max_jobs = max_descriptors - 5; + + path = _path; + + /* Move the server to the socket directory */ + dirpath = malloc(strlen(path) + 1); + strcpy(dirpath, path); + chdir(dirname(dirpath)); + free(dirpath); + + nconnections = 0; + + 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); + + res = bind(ls, (struct sockaddr *)&addr, sizeof(addr)); + if (res == -1) + error("Error binding."); + + res = listen(ls, 0); + if (res == -1) + error("Error listening."); + + // 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; + user_queue[i] = 0; + } + // jobDB_num = jobDB_wait_num = 0; + // 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); + + install_sigterm_handler(); + + set_default_maxslots(); + + initialize_log_dir(); + + if (notify_fd != 0) notify_parent(notify_fd); - server_loop(ls); + 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) - 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; + // wait the connection + 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; + + client_cs[nconnections].ts_UID = get_tsUID(scred.uid); + + if (client_cs[nconnections].ts_UID == -1) { + close(cs); + } else { + nconnections++; + } } - end_server(ls); + 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 */ + // 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; + } + } else { + /* + if (client_cs[i].hasjob && check_running_dead(client_cs[i].jobid) == 1) { + clean_after_client_disappeared(client_cs[i].socket, i); + } + */ + } + } // nconnections + + /* 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); + + 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); + } + // printf("end of next_run_job for jobid[%d]\n", newjob); + } // job != -1 + s_check_holdon(); + } // end of while (keep_loop) + + 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); + close_sqlite(); + /* 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_delete_job(client_cs[index].jobid); + } - for (i = index; i < (nconnections - 1); ++i) { - memcpy(&client_cs[i], &client_cs[i + 1], sizeof(client_cs[0])); + 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 s_remove_all_queues(int ts_UID) { + int i = 0; + while(i < nconnections - 1) { + if (ts_UID == 0 || client_cs[i].ts_UID == ts_UID) { + if (job_is_running(client_cs[i].jobid) != 1) { + clean_after_client_disappeared(client_cs[i].socket, i); + } else { + i++; // To next one + } + } else { + i++; // To next one } - 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); +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) { + warning("client recv failed"); + clean_after_client_disappeared(s, index); + return NOBREAK; + } else if (res == 0) { + 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; + + // Time-out unlock + if (user_locker > 0) { // locked by no-root user + time_t dt = time(NULL) - locker_time; + if (user_locker == 0 && dt > DEFAULT_ROOT_LOCK_TIME) { + user_locker = -1; + } else if (dt > DEFAULT_USER_LOCK_TIME) { + user_locker = -1; + } + } + - close(socket); + /* Process message */ + switch (m.type) { + case REFRESH_USERS: + if (ts_UID == 0) { + s_refresh_users(s); + } + close(s); remove_connection(index); -} + break; + case LOCK_SERVER: + s_lock_server(s, ts_UID); + close(s); + remove_connection(index); + break; + case UNLOCK_SERVER: + s_unlock_server(s, ts_UID); + close(s); + remove_connection(index); + break; + case HOLD_JOB: + s_hold_job(s, m.jobid, ts_UID); + close(s); + remove_connection(index); + break; + case CONT_JOB: + s_cont_job(s, m.jobid, ts_UID); + close(s); + remove_connection(index); + break; + case SUSPEND_USER: + // Root, uid in m.jobid + if (ts_UID == 0) { + if (m.jobid != 0) { + s_suspend_user(s, get_tsUID(m.jobid)); + s_user_status(s, get_tsUID(m.jobid)); + } else { + s_suspend_user_all(s); + s_user_status_all(s); + } + } else { + s_suspend_user(s, ts_UID); + s_user_status(s, ts_UID); + } + close(s); + remove_connection(index); + break; + case RESUME_USER: + if (ts_UID == 0) { + if (m.jobid != 0) { + s_resume_user(s, get_tsUID(m.jobid)); + s_user_status(s, get_tsUID(m.jobid)); + } else { + s_resume_user_all(s); + s_user_status_all(s); + } + } else { + s_resume_user(s, ts_UID); + s_user_status(s, ts_UID); + } + close(s); + remove_connection(index); + break; + case KILL_SERVER: + 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. + ts_UID = s_check_relink(s, m.u.newjob.taskpid, ts_UID); + } else { + if (s_check_locker(ts_UID) == 1) { break; } + } -static enum Break -client_read(int index) { - struct Msg m = default_msg(); - int s; - int res; + 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; + } - s = client_cs[index].socket; + client_cs[index].jobid = s_newjob(s, &m, ts_UID); + client_cs[index].hasjob = 1; - /* 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; + 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) { + 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"); } + if (m.u.output.pid > 0) + s_process_runjob_ok(client_cs[index].jobid, buffer, m.u.output.pid); + } break; + case KILL_ALL: + 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; + 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; + s_list(s, 0, m.u.list.list_format); // list all - /* 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); + /* We must actively close, meaning End of Lines */ + close(s); + remove_connection(index); + break; + case INFO: + s_job_info(s, m.jobid); + close(s); + remove_connection(index); + break; + case LAST_ID: + s_send_last_id(s); + break; + case GET_LABEL: + s_get_label(s, m.jobid); + break; + case GET_CMD: + 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. */ + client_cs[index].hasjob = 0; + break; + case CLEAR_FINISHED: + s_clear_finished(ts_UID); + break; + case ASK_OUTPUT: + 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.jobid, ts_UID); + if (went_ok) { + int i; + for (i = 0; i < nconnections; ++i) { + 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 */ + 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.jobid); + break; + case WAIT_RUNNING_JOB: + s_wait_running_job(s, m.jobid); + break; + case COUNT_RUNNING: + s_count_running_jobs(s, ts_UID); + break; + case URGENT: + if (ts_UID == 0 || ts_UID == s_get_job_tsUID(m.jobid)) { + s_move_urgent(s, m.jobid); + } + if (jobsort_flag) + s_sort_jobs(); + close(s); + remove_connection(index); + break; + case SET_MAX_SLOTS: + if (ts_UID == 0) + s_set_max_slots(s, m.u.max_slots); + close(s); + remove_connection(index); + break; + case GET_MAX_SLOTS: + s_get_max_slots(s); + break; + case SWAP_JOBS: + if (ts_UID == 0) { + s_swap_jobs(s, m.u.swap.jobid1, m.u.swap.jobid2); + } else { + 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); + } + } + if (jobsort_flag) + s_sort_jobs(); + close(s); + remove_connection(index); + break; + case GET_STATE: + s_send_state(s, m.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_send_runjob(s, jobid); + s = client_cs[index].socket; + 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.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..984b753 100644 --- a/server_start.c +++ b/server_start.c @@ -4,177 +4,224 @@ 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" int server_socket; - static char *socket_path; static int should_check_owner = 0; static int fork_server(); void create_socket_path(char **path) { - 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")); - - /* 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 ... */ - - /* Create the path */ - tmpdir = getenv("TMPDIR"); - if (tmpdir == NULL) - tmpdir = "/tmp"; - - sprintf(userid, "%u", (unsigned int) getuid()); + char *tmpdir; + char userid[20] = "root"; + 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")); + + /* 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 ... */ + /* Create the path */ + tmpdir = getenv("TMPDIR"); + if (tmpdir == NULL) + tmpdir = "/tmp"; + + /* Calculate the size */ + size = strlen(tmpdir) + strlen("/socket-ts.") + strlen(userid) + 1; + + /* Freed after preparing the socket address */ + *path = (char *)malloc(size); + + snprintf(*path, size, "%s/socket-ts.%s", tmpdir, userid); + + should_check_owner = 1; +} - /* Calculate the size */ - size = strlen(tmpdir) + strlen("/socket-ts.") + strlen(userid) + 1; +int try_connect(int s) { + struct sockaddr_un addr; + int res; - /* Freed after preparing the socket address */ - *path = (char *) malloc(size); + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, socket_path); - sprintf(*path, "%s/socket-ts.%s", tmpdir, userid); + res = connect(s, (struct sockaddr *)&addr, sizeof(addr)); - should_check_owner = 1; + return res; } -int try_connect(int s) { - struct sockaddr_un addr; - int res; +void c_check_daemon() { + int res; + server_socket = socket(AF_UNIX, SOCK_STREAM, 0); + if (server_socket == -1) + error("getting the server socket"); - addr.sun_family = AF_UNIX; - strcpy(addr.sun_path, socket_path); + create_socket_path(&socket_path); - res = connect(s, (struct sockaddr *) &addr, sizeof(addr)); + res = try_connect(server_socket); - return res; + /* 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; +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); +} + +static void server_info() { + 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()); + printf(" Sqlite Database @ %s [TS_SQLITE_PATH]\n", get_sqlite_path()); +} + +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; - 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_info(); + server_main(p[1], socket_path); + exit(0); + break; + case -1: /* Error */ + return -1; + default: /* Parent */ + server_info(); + 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; - - 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) { - 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 (errno == ECONNREFUSED) - unlink(socket_path); - - /* 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); - } - +int ensure_server_up(int daemonFlag) { + int res; + int notify_fd; + server_socket = socket(AF_UNIX, SOCK_STREAM, 0); + if (server_socket == -1) + 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 */ + if (res == 0) { + try_check_ownership(); free(socket_path); - - /* Good connection on the second time */ return 1; + } + + /* If error other than "No one listens on the other end"... */ + if (!(errno == ENOENT || errno == ECONNREFUSED)) + 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 (getuid() == 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("Running the Task-Spooler server as the ROOT user is the only " + "allowed option.\n"); + } + + 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); + } + free(socket_path); + + /* Good connection on the second time */ + return 1; } diff --git a/sqlite.c b/sqlite.c new file mode 100644 index 0000000..e2c5053 --- /dev/null +++ b/sqlite.c @@ -0,0 +1,500 @@ +#include +#include +#include +#include + +#include "default.inc" +#include "main.h" + +sqlite3 *db = NULL; +char sql[1024*16] = ""; + +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 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; +} + +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; + 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); + err[0] = -1; + } else { + err[0] = 0; + } + return value; +} + +static int max_order_id(int* err) { return check_order_id("MAX", err); } + +static int min_order_id(int* err) { return check_order_id("MIN", err); } + +static int get_order_id(int jobid, int* err) { + char *err_msg = 0; + 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); + err[0] = -1; + } else { + err[0] = 0; + } + return value; +} + +int close_sqlite() { + // free(jobDB_Jobs); + return sqlite3_close(db); +} + +int open_sqlite() { + const char *path = get_sqlite_path(); + 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); + } + + 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); + error_flag--; + } 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," + "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); + error_flag--; + } 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); + // error_flag--; + } + + return error_flag; +} + +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; // default value + } + return value; +} + +// return error code +int set_jobids_DB(int value) { + char *err_msg = 0; + 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) { + 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; + const char *email = job->email == NULL ? "(..)" : job->email; + 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, + 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_or_replace_DB(struct Job *job, const char *table) { + return edit_DB(job, table, "INSERT OR REPLACE"); +} + +//return error code +static int set_order_id_DB(int jobid, int order_id) { + char *err_msg = 0; + 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; +} + +//return error code +int set_state_DB(int jobid, int state) { + char *err_msg = 0; + 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; +} + +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; +} + +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); +} + +/* +static void clear_DB(const char* table) { + 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); + } +} +*/ + +// return error code +int read_jobid_DB(int **jobids, const char *table) { + int n; + 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; // 返回-2表示查询失败 + } + + 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); +#ifdef TASKSET + job->cores = NULL; +#endif + struct Result *result = &(job->result); + struct Procinfo *info = &(job->info); + + 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 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)); + copy_with_nullcheck(&(job->command), sql); + + job->state = sqlite3_column_int(stmt, 2); + + 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 { + 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)); + 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表示查询成功 +} 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 diff --git a/taskset.c b/taskset.c new file mode 100644 index 0000000..adc388a --- /dev/null +++ b/taskset.c @@ -0,0 +1,109 @@ +#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 + +#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}; +static struct Job *core_jobs[MAX_CORE_NUM] = {NULL}; +#endif + +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() { +#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) { + 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++; + } + 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; +} +#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--; + } + } + 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; +#ifdef TASKSET + 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); + + kill_pids(p->pid, -1, cmd); + p->cores = core_str; + free(cmd); +#endif + return 0; +} 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/ttail.c b/ttail.c deleted file mode 100644 index e69de29..0000000 diff --git a/user.c b/user.c new file mode 100644 index 0000000..5b54189 --- /dev/null +++ b/user.c @@ -0,0 +1,346 @@ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "default.inc" +#include "main.h" +#include "user.h" + +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"); + if (str == NULL || strlen(str) == 0) { + return DEFAULT_USER_PATH; + } else { + return str; + } +} + +int get_env(const char *env, int v0) { + char *str; + str = getenv(env); + if (str == NULL || strlen(str) == 0) { + return v0; + } else { + int i = atoi(str); + if (i < 0) + i = v0; + return i; + } +} + +//按空格自动分割子串的函数 +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. -/ + 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) { + 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) { + logfile_path = DEFAULT_LOG_PATH; + } + return logfile_path; +} + +void check_relink(int pid) { + 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 = NULL; +} + +/* + 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"); + if (f == NULL) { + return; + } + 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); + 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[ts_UID], p->num_slots, label, p->pid, p->command, buf); + 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); +} + +/* +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; +} +*/ + +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 = 999; + } + + printf("last line is %s with jobid = %d\n", line, jobid); + fclose(fp); + return jobid + 1; +} + +void read_user_file(const char *path) { + server_uid = getuid(); + // if (server_uid != root_UID) { + // error("the service is not run as root!"); + //} + FILE *fp; + fp = fopen(path, "r"); + if (fp == NULL) + exit(EXIT_FAILURE); + char *line = NULL; + size_t len = 0; + size_t read; + int UID, slots; + char name[USER_NAME_WIDTH]; + + 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); + continue; + } else { + int ts_UID = get_tsUID(UID); + if (ts_UID == -1) { + if (user_number >= USER_MAX) + continue; + + ts_UID = user_number; + user_number++; + + 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[ts_UID] = slots; + } + } + } + + fclose(fp); + if (line) + free(line); +} + +const char *uid2user_name(int uid) { + // if (uid == 0) + // return "Root"; + int ts_UID = get_tsUID(uid); + if (ts_UID != -1) { + return user_name[ts_UID]; + } else { + return "Unknown"; + } +} + +void s_user_status_all(int s) { + char buffer[256]; + char *extra; + 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); + send_list_line(s, buffer); + } + snprintf(buffer, 256, "Service at UID:%d\n", server_uid); + send_list_line(s, buffer); +} + +// i = ts_UID; +void s_user_status(int s, int i) { + char buffer[256]; + char *extra = ""; + 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); + send_list_line(s, buffer); +} + +int get_tsUID(int uid) { + for (int i = 0; i < user_number; i++) { + if (uid == user_UID[i]) { + return i; + } + } + return -1; +} + +void kill_pids(int parent_pid, int signal, const char* cmd) { + 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_pids(cPID, signal, cmd); + } + fclose(file); + } + closedir(dir); + + if (signal >= 0 && kill(parent_pid, signal) != 0) { + printf("kill %d -%d ", parent_pid, signal); + perror("Error resuming process"); + } + + if (cmd != NULL) { + char buf[1024] = ""; + snprintf(buf, 1024, "%s %d", cmd, parent_pid); + printf("%s\n", buf); + int result = system(buf); + + if (result == -1) { + ; // perror("Error executing command"); + } + } +} diff --git a/user.h b/user.h new file mode 100644 index 0000000..4bc2747 --- /dev/null +++ b/user.h @@ -0,0 +1,20 @@ +#define USER_NAME_WIDTH 256 +#define USER_MAX 100 +#include + +char user_name[USER_MAX][USER_NAME_WIDTH]; // the linux user name +int server_uid; +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; + +struct ucred { + uint32_t pid; + uint32_t uid; + uint32_t gid; +}; \ No newline at end of file diff --git a/user.txt b/user.txt new file mode 100644 index 0000000..8896eb7 --- /dev/null +++ b/user.txt @@ -0,0 +1,10 @@ +# 1231 # comments +TS_SLOTS = 16 # environment variable +TS_FIRST_JOBID = 2000 # environment variable +# uid name slots +1000 Kylin 10 +3021 test1 10 +1001 test0 100 +34 user2 10 + +qweq qweq qweq # automatically skipped diff --git a/version.h b/version.h new file mode 100644 index 0000000..773c942 --- /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.1.1a +#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