util/pretty-svstat

first commit (f34e3130ad3a9d9de0a2fb822748d72fb5ee9bca)
Repositories | README.md | LICENSE

commit f34e3130ad3a9d9de0a2fb822748d72fb5ee9bca
Author: Friedel Schön <[email protected]>
Date:   Fri, 14 Jun 2024 15:41:46 +0200

first commit

Diffstat:
ALICENSE18++++++++++++++++++
AMakefile24++++++++++++++++++++++++
AREADME.md97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apsvstat.192+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apsvstat.c151+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 382 insertions(+), 0 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -0,0 +1,17 @@ +Copyright (c) 2023 Friedel Schön <[email protected]> + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +\ No newline at end of file diff --git a/Makefile b/Makefile @@ -0,0 +1,24 @@ +all: psvstat + +%.o: %.c + $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $^ + +psvstat: psvstat.o + $(CC) $^ -o $@ $(LDFLAGS) + +clean: + rm -f psvstat psvstat.o + +install: all + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f psvstat $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/stw + mkdir -p $(DESTDIR)$(PREFIX)/man/man1 + sed "s/VERSION/$(VERSION)/g" < psvstat.1 > $(DESTDIR)$(PREFIX)/share/man/man1/psvstat.1 + chmod 644 $(DESTDIR)$(PREFIX)/share/man/man1/psvstat.1 + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/psvstat \ + $(DESTDIR)$(PREFIX)/share/man/man1/psvstat.1 + +.PHONY: all clean install uninstall diff --git a/README.md b/README.md @@ -0,0 +1,96 @@ +# psvstat - Pretty Service Status + +`psvstat` is a command-line utility for checking and printing the status of `runit` services. This tool reads the service status from the `supervise/status` file of each specified `runit` service and displays relevant information in a user-friendly format. + +## Features + +- Displays whether the service is a user service or a system service. +- Shows the service name, desired state, and current state. +- Indicates if the service is paused, down, running, or finished. +- Provides the time elapsed since the last status change. +- Displays the process ID and command name of the running service. + +## Compilation + +To compile the program, run the following command: + +```sh +$ make +$ make PREFIX=$HOME/.local install +``` + +## Usage + +The `psvstat` program takes one or more arguments, each representing the path to a `runit` service directory. For example: + +```sh +psvstat /etc/service/service1 /etc/service/service2 +``` + +### Arguments + +- **service_path**: The path to the `runit` service directory. The program expects the status file to be located at `service_path/supervise/status`. + +## Output + +The program prints the status of each specified service in a formatted manner. The output contains the following columns: + +1. **Type**: Indicates if the service is a user service (`user`) or a system service (`sys`). +2. **Name**: The name of the service. +3. **Desired State**: Indicates if the service's desired state matches its current state. + - `=`: The service's desired state matches its current state. + - `v`: The service is up but should be down. + - `^`: The service is down but should be up. +4. **Current State**: The current state of the service. + - `paus`: The service is paused. + - `down`: The service is down. + - `run`: The service is running. + - `fin`: The service has finished. + - `???`: Unknown state. +5. **Time Since Last Change**: The time elapsed since the last status change. +6. **PID**: The process ID of the service if it is running. +7. **Command**: The command name of the running process if available. + +### Example Output + +```sh +sys service1 = run 2 hours 1234 myservice +sys service2 ^ down 5 minutes --- --- +``` + +In this example: +- `service1` is a system service (`sys`), its desired state matches its current state (`=`), it is currently running (`run`), the status changed 2 hours ago, its PID is 1234, and the command name is `myservice`. +- `service2` is a system service (`sys`), its desired state does not match its current state (`v`), it should be down but is up, the status changed 5 minutes ago, it is not running (`---`), and no command name is available (`---`). + +## Error Handling + +The program handles several error conditions and prints appropriate messages to `stderr`: + +- If it is unable to open the `supervise/status` file, it prints: + ``` + <service_path>: unable to open supervise/status + ``` +- If it is unable to read the `supervise/status` file, it prints: + ``` + <service_path>: unable to read status + ``` + +## Environment Variables + +- **HOME**: Used to determine if a service is a user service. + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. + +## Contributing + +Contributions are welcome! Please open an issue or submit a pull request on GitHub. + +## Author + +Written by Friedel Schon. + +## Acknowledgments + +Thanks to the developers and community of `runit` for their excellent software and documentation. +\ No newline at end of file diff --git a/psvstat.1 b/psvstat.1 @@ -0,0 +1,92 @@ +.TH psvstat 1 "June 2024" "1.0" "psvstat Manual" +.SH NAME +psvstat \- a utility to print the status of runit services +.SH SYNOPSIS +.B psvstat +.I service_path ... +.SH DESCRIPTION +.B psvstat +is a command-line utility for checking and printing the status of \fBrunit\fP services. It reads the service status from the \fBsupervise/status\fP file of each specified \fBrunit\fP service and displays relevant information. +.SH OPTIONS +.TP +.I service_path +The path to the \fBrunit\fP service directory. The program expects the status file to be located at \fIservice_path/supervise/status\fP. +.SH OUTPUT +The program prints the status of each specified service in a formatted manner. The output contains the following columns: +.TP +.B Type +Indicates if the service is a user service (\fBuser\fP) or a system service (\fBsys\fP). +.TP +.B Name +The name of the service. +.TP +.B Desired State +Indicates if the service's desired state matches its current state. +.RS +.TP +\fB=\fP +The service's desired state matches its current state. +.TP +\fBv\fP +The service is up but should be down. +.TP +\fB^\fP +The service is down but should be up. +.RE +.TP +.B Current State +The current state of the service. +.RS +.TP +\fBpaus\fP +The service is paused. +.TP +\fBdown\fP +The service is down. +.TP +\fBrun\fP +The service is running. +.TP +\fBfin\fP +The service has finished. +.TP +\fB???\fP +Unknown state. +.RE +.TP +.B Time Since Last Change +The time elapsed since the last status change. +.TP +.B PID +The process ID of the service if it is running. +.TP +.B Command +The command name of the running process if available. +.PP +.SH EXAMPLES +.B psvstat /etc/service/service1 /etc/service/service2 +.PP +In this example: +.TP +\fBservice1\fP is a system service (\fBsys\fP), its desired state matches its current state (\fB=\fP), it is currently running (\fBrun\fP), the status changed 2 hours ago, its PID is 1234, and the command name is \fBmyservice\fP. +.TP +\fBservice2\fP is a system service (\fBsys\fP), its desired state does not match its current state (\fBv\fP), it should be down but is up, the status changed 5 minutes ago, it is not running (\fB---\fP), and no command name is available (\fB---\fP). +.SH ERROR HANDLING +The program handles several error conditions and prints appropriate messages to \fBstderr\fP: +.TP +.B <service_path>: unable to open supervise/status +Printed if the program is unable to open the \fBsupervise/status\fP file. +.TP +.B <service_path>: unable to read status +Printed if the program is unable to read the \fBsupervise/status\fP file. +.SH ENVIRONMENT +.TP +.B HOME +Used to determine if a service is a user service. +.SH EXIT STATUS +The program does not return any specific exit status codes but prints error messages for issues encountered during execution. +.SH SEE ALSO +.BR runsv (8), +.BR sv (8) +.SH AUTHOR +Written by Friedel Schon. diff --git a/psvstat.c b/psvstat.c @@ -0,0 +1,151 @@ +#include <fcntl.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +struct service_serial { + uint8_t status_change[8]; + uint8_t status_change_ms[4]; + uint8_t pid[4]; + uint8_t paused; + uint8_t wantsup; + uint8_t terminated; + uint8_t state; +}; + +void printstatus(const char* path, struct service_serial* buffer) { + const char* name; + if ((name = strrchr(path, '/'))) { + if (!strcmp(name, "/log")) { + while (--name > path) { + if (*name == '/') + break; + } + name++; + } else { + name++; + } + } else + name = path; + + char* home = getenv("HOME"); + int isuser = home != NULL && strncmp(home, path, strlen(home)) == 0; + + if (isuser) { + printf("user "); + } else { + printf("sys "); + } + + printf("%-20s ", name); + + // wants up and is up + // wants down and is down + if ((buffer->wantsup == 'd') == (buffer->state == 0)) + printf("= "); + // wants down and is up + else if (buffer->wantsup == 'd') + printf("v "); + // wants up and is down + else if (buffer->state == 0) + printf("^ "); + + if (buffer->paused) + printf("paus "); + else if (buffer->state == 0) + printf("down "); + else if (buffer->state == 1) + printf("run "); + else if (buffer->state == 2) + printf("fin "); + else + printf("??? "); + + uint64_t tai = ((uint64_t) buffer->status_change[0] << 56) | + ((uint64_t) buffer->status_change[1] << 48) | + ((uint64_t) buffer->status_change[2] << 40) | + ((uint64_t) buffer->status_change[3] << 32) | + ((uint64_t) buffer->status_change[4] << 24) | + ((uint64_t) buffer->status_change[5] << 16) | + ((uint64_t) buffer->status_change[6] << 8) | + ((uint64_t) buffer->status_change[7] << 0); + + time_t timediff = time(NULL) - tai + 4611686018427387914ULL; + const char* timediffu = timediff == 1 ? "second" : "seconds"; + if (timediff >= 60) { + timediff /= 60; + timediffu = timediff == 1 ? "minute" : "minutes"; + if (timediff >= 60) { + timediff /= 60; + timediffu = timediff == 1 ? "hour" : "hours"; + if (timediff >= 24) { + timediff /= 24; + timediffu = timediff == 1 ? "day" : "days"; + } + } + } + char timediffstr[20]; + snprintf(timediffstr, sizeof timediffstr, "%ld %s", timediff, timediffu); + + printf("%-11s ", timediffstr); + + if (buffer->state == 1) { + pid_t pid = (buffer->pid[0] << 0) | + (buffer->pid[1] << 8) | + (buffer->pid[2] << 16) | + (buffer->pid[3] << 24); + + printf("%-5d ", pid); + + int procfd; + char procpath[PATH_MAX]; + char cmdline[1024]; + int nread; + snprintf(procpath, sizeof procpath, "/proc/%d/comm", pid); + + if ((procfd = open(procpath, O_RDONLY)) != -1) { + nread = read(procfd, cmdline, sizeof cmdline); + if (nread < 0) nread = 0; + if (nread == sizeof cmdline) { + strcpy(&cmdline[sizeof cmdline - 4], "..."); + } else { + nread--; + cmdline[nread] = '\0'; + } + close(procfd); + printf("%s", cmdline); + } else { + printf("---"); + } + } else { + printf("--- ---"); + } + printf("\n"); +} + +int main(int argc, char** argv) { + char path[PATH_MAX]; + int fd; + char* basename; + + struct service_serial statusbuf; + + for (int i = 1; i < argc; i++) { + snprintf(path, sizeof path, "%s/supervise/status", argv[i]); + if ((fd = open(path, O_RDONLY)) == -1) { + fprintf(stderr, "%s: unable to open supervise/status\n", argv[i]); + continue; + } + if (read(fd, &statusbuf, sizeof statusbuf) != sizeof statusbuf) { + fprintf(stderr, "%s: unable to read status\n", argv[i]); + continue; + } + close(fd); + + printstatus(argv[i], &statusbuf); + } +}