1 | #define _POSIX_SOURCE
|
2 | #include <errno.h>
|
3 | #include <regex.h>
|
4 | #include <signal.h>
|
5 | #include <stdbool.h>
|
6 | #include <stdio.h>
|
7 | #include <stdlib.h>
|
8 | #include <string.h>
|
9 | #include <sys/socket.h>
|
10 | #include <sys/time.h>
|
11 | #include <sys/types.h>
|
12 | #include <sys/un.h>
|
13 | #include <sys/wait.h>
|
14 | #include <unistd.h>
|
15 |
|
16 | #define _MONITOR_REGEXP "unix:\\([-/.[:alnum:]]*\\)"
|
17 | #define _SHUTDOWN_CMD "system_powerdown\n"
|
18 | #define _GRACE_PERIOD 15
|
19 | #define _MAX_SOCKET_TIMEOUT 5
|
20 |
|
21 | struct _runtime {
|
22 | char *socket_path;
|
23 | int *pid;
|
24 | };
|
25 |
|
26 | struct _runtime _opts;
|
27 |
|
28 | static void _check_code_and_exit(int status) {
|
29 | int result = WEXITSTATUS(status);
|
30 | if (result == 0) {
|
31 | puts("child process exited normally");
|
32 | exit(EXIT_SUCCESS);
|
33 | } else {
|
34 | printf("child process exited with code: %d\n", result);
|
35 | exit(EXIT_FAILURE);
|
36 | }
|
37 | }
|
38 |
|
39 | static void _send_shutdown(char *path) {
|
40 | int s, len;
|
41 | struct sockaddr_un remote = {
|
42 | .sun_family = AF_UNIX,
|
43 | };
|
44 | char str[100];
|
45 | if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
|
46 | perror("socket");
|
47 | exit(1);
|
48 | }
|
49 | struct timeval tv;
|
50 | tv.tv_sec = _MAX_SOCKET_TIMEOUT;
|
51 | tv.tv_usec = 0;
|
52 | setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv);
|
53 | strcpy(remote.sun_path, _opts.socket_path);
|
54 | len = strlen(remote.sun_path) + sizeof(remote.sun_family);
|
55 | if (connect(s, (struct sockaddr *)&remote, len) == -1) {
|
56 | perror("connect");
|
57 | return;
|
58 | }
|
59 | printf("connected to %s\n", _opts.socket_path);
|
60 | if (send(s, _SHUTDOWN_CMD, strlen(_SHUTDOWN_CMD) + 1, 0) == -1) {
|
61 | perror("send");
|
62 | return;
|
63 | }
|
64 |
|
65 | for (;;) {
|
66 | len = recv(s, str, sizeof(str) - 1, 0);
|
67 | switch (len) {
|
68 | case -1:
|
69 | perror("recv");
|
70 | close(s);
|
71 | return;
|
72 | case 0:
|
73 | close(s);
|
74 | return;
|
75 | }
|
76 | }
|
77 |
|
78 | close(s);
|
79 | }
|
80 |
|
81 | static void _shutdown() {
|
82 | _send_shutdown(_opts.socket_path);
|
83 | printf("process %d has %d seconds to shutdown\n", *_opts.pid, _GRACE_PERIOD);
|
84 | int n_seconds = 0;
|
85 | while (n_seconds < _GRACE_PERIOD) {
|
86 | int stat;
|
87 | if (waitpid(*_opts.pid, &stat, WNOHANG) == -1) {
|
88 | printf("child process stopped, good bye!\n");
|
89 | _check_code_and_exit(stat);
|
90 | };
|
91 | sleep(2);
|
92 | n_seconds = n_seconds + 2;
|
93 | }
|
94 | printf("grace period ended, sending SIGTERM\n");
|
95 | kill(*_opts.pid, SIGTERM);
|
96 | }
|
97 |
|
98 | static void signal_handler(int sig) {
|
99 | printf("handling signal %d\n", sig);
|
100 | switch (sig) {
|
101 | case SIGTERM:
|
102 | _shutdown();
|
103 | case SIGINT:
|
104 | _shutdown();
|
105 | }
|
106 | }
|
107 |
|
108 | char *_get_arg_by_name(int argc, char *argv[], char *name) {
|
109 | char *res;
|
110 | int i;
|
111 | for (i = 0; i < argc; i++) {
|
112 | if (strcmp(argv[i], name) == 0 && (i < argc)) {
|
113 | res = argv[i + 1];
|
114 | return res;
|
115 | }
|
116 | }
|
117 | return NULL;
|
118 | }
|
119 |
|
120 | char *_get_arg_with_regexp(int argc, char *argv[], char *name,
|
121 | char *expression) {
|
122 | char *arg_raw;
|
123 | arg_raw = _get_arg_by_name(argc, argv, name);
|
124 | if (arg_raw == NULL) {
|
125 | return NULL;
|
126 | }
|
127 |
|
128 | regex_t regex;
|
129 | regmatch_t groups[2];
|
130 |
|
131 | if (regcomp(®ex, expression, 0) != 0) {
|
132 | fprintf(stderr, "regexp failed to compile\n");
|
133 | exit(1);
|
134 | }
|
135 |
|
136 | char *result = malloc(strlen(arg_raw) + 1);
|
137 |
|
138 | if (regexec(®ex, arg_raw, 2, groups, 0) == 0) {
|
139 | if (groups[1].rm_so == (size_t)-1) {
|
140 | fprintf(stderr, "cannot match against flag:'%s'\n", arg_raw);
|
141 | exit(1);
|
142 | }
|
143 | strcpy(result, arg_raw);
|
144 | result[groups[1].rm_eo] = 0;
|
145 | result = result + groups[1].rm_so;
|
146 | } else {
|
147 | fprintf(stderr, "cannot match against flag:'%s'\n", arg_raw);
|
148 | exit(1);
|
149 | }
|
150 |
|
151 | regfree(®ex);
|
152 | return result;
|
153 | }
|
154 |
|
155 | void _do_fork(char *argv[], int *pid) {
|
156 | pid_t child_pid;
|
157 | if ((child_pid = fork()) == -1) {
|
158 | perror("fork");
|
159 | exit(1);
|
160 | }
|
161 | if (child_pid == 0) {
|
162 | setpgid(child_pid, child_pid);
|
163 | execvp(argv[0], argv);
|
164 | } else {
|
165 | *pid = child_pid;
|
166 | }
|
167 | }
|
168 |
|
169 | int main(int argc, char *argv[]) {
|
170 | char *monitor_path = NULL;
|
171 | monitor_path = _get_arg_with_regexp(argc, argv, "-monitor", _MONITOR_REGEXP);
|
172 | if (monitor_path == NULL) {
|
173 | printf("-monitor flag was not specified\n");
|
174 | return EXIT_FAILURE;
|
175 | }
|
176 | _opts.socket_path = monitor_path;
|
177 | char *_copy_args[argc];
|
178 | _copy_args[argc - 1] = NULL;
|
179 | for (int i = 1; i < argc; ++i) {
|
180 | _copy_args[i - 1] = argv[i];
|
181 | }
|
182 |
|
183 | signal(SIGTERM, signal_handler);
|
184 | signal(SIGINT, signal_handler);
|
185 |
|
186 | int pid = 0;
|
187 | int stat;
|
188 |
|
189 | _do_fork(_copy_args, &pid);
|
190 | _opts.pid = &pid;
|
191 | printf("monitoring pid %d\n", pid);
|
192 | while (waitpid(pid, &stat, 0) == -1) {
|
193 | if (errno != EINTR) {
|
194 | stat = -1;
|
195 | break;
|
196 | }
|
197 | }
|
198 | _check_code_and_exit(stat);
|
199 | }
|