3 throttle.c -- throttle utility to make use of taskd.pl throttle services
4 Copyright (C) 2015 Kyle J. McKay. All rights reserved.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 This utility provides a means to make use of taskd.pl's throttle services.
24 It is simply started with options specifying the throttle class, the item
25 description and an optional supplementary message to included in case the
28 If the request is allowed to proceed, the non-option arguments specify a
29 command and arguments to be execvp'd. If the request is throttled, a result
30 suitable for a CGI is written to stdout with a 503 status. Alternatively
31 on failure a non-zero exit code can be returned in lieu of CGI output.
35 #error SOCKET_FILE must be defined to full path to taskd.pl socket
38 #ifndef _POSIX_C_SOURCE
39 #define _POSIX_C_SOURCE 200809
50 #include <sys/types.h>
51 #include <sys/socket.h>
53 #include <sys/select.h>
57 #define FD_COPY(src,dst) ((*dst)=(*src))
61 "Usage: throttle [-t] [-p] [-e] -c <class> -d <desc> [-m <msg>] command [arg]...\n" \
62 " -h show this help\n" \
63 " -e exit with error (3 = exec fail, 10 = connect fail,\n" \
64 " 11 = request fail, 20 = request error, 21 = throttled)\n" \
65 " on failure but do not output anything to standard output\n" \
66 " -t throttle on connect failure (default is proceed)\n" \
67 " -p proceed on throttle request error (default is throttle)\n" \
68 " -c <class> required throttle class\n" \
69 " -d <desc> required description of item in class\n" \
70 " -m <msg> supplementary message to include with 503 error\n" \
71 " command [arg]... command and arguments to execvp on proceed\n"
73 static int ok_connect_fail
= 1;
74 static int ok_request_fail
= 0;
75 static int err_on_failure
= 0;
81 fprintf(stderr
, "fatal: %s\n", msg
? msg
: "error");
89 fprintf(stderr
, "fatal: %s: %s\n", msg
? msg
: "failed", strerror(errno
));
97 fprintf(stderr
, "fatal: %s\n", msg
? msg
: "error");
98 fprintf(stderr
, "%s", USAGE
);
106 return ('A' <= ch
&& ch
<= 'Z') ||
107 ('a' <= ch
&& ch
<= 'z');
119 while ((ch
= *str
++) != 0) {
130 while (*++str
== '\n')
132 ans
+= 10; /* strlen("\n</p>\n<p>\n") */
155 bl
= get_escxml_size(msg
);
156 ans
= (char *)malloc(bl
+ 1);
158 diefailure("malloc failed");
160 while ((ch
= *msg
++) != 0) {
163 strncpy(p
, "<", 4);
167 strncpy(p
, ">", 4);
171 strncpy(p
, "&", 5);
176 while (*++msg
== '\n')
178 strncpy(p
, "\n</p>\n<p>\n", 10);
192 static sig_atomic_t piped
= 0;
193 static int wakeup
[2] = {-1, -1};
194 #define wakeup_r (wakeup[0])
195 #define wakeup_w (wakeup[1])
205 ignore
= write(wakeup_w
, "!", 1);
213 if (fcntl(fd
, F_SETFD
, FD_CLOEXEC
))
214 diefailure("fcntl F_SETFD failed");
221 int flags
= fcntl(fd
, F_GETFL
, 0);
223 diefailure("fcntl F_GETFL failed");
224 if (fcntl(fd
, F_SETFL
, flags
| O_NONBLOCK
) == -1)
225 diefailure("fcntl F_SETFL O_NONBLOCK failed");
232 int flags
= fcntl(fd
, F_GETFL
, 0);
234 diefailure("fcntl F_GETFL failed");
235 if (fcntl(fd
, F_GETFL
, flags
& ~O_NONBLOCK
) == -1)
236 diefailure("fcntl F_SETFL !O_NONBLOCK failed");
245 const char *buf
= (const char *)_buf
;
248 size_t count
= nbyte
;
253 written
= write(fd
, buf
+off
, count
);
255 count
-= (size_t)written
;
256 off
+= (size_t)written
;
258 } while(!piped
&& count
&& (written
> 0 || errno
== EINTR
));
259 if (!count
&& !piped
)
268 #define GENERIC_MESSAGE \
269 "The requested service is temporarily unavailable, please try again later."
273 int err
, const char *msg
)
277 printf("%s\r\n", "Status: 503 Service Temporarily Unavailable");
278 printf("%s\r\n", "Expires: Fri, 01 Jan 1980 00:00:00 GMT");
279 printf("%s\r\n", "Pragma: no-cache");
280 printf("%s\r\n", "Cache-Control: no-cache,max-age=0,must-revalidate");
281 printf("%s\r\n", "Content-Type: text/html; charset=utf-8");
282 printf("%s\r\n", "");
283 printf("%s\n%s\n%s\n", "<p>", GENERIC_MESSAGE
, "</p>");
285 printf("%s\n%s\n%s\n", "<p>", msg
, "</p>");
296 const char *classname
= NULL
;
297 const char *desc
= NULL
;
302 struct sockaddr_un sun
;
307 while ((ch
= getopt(argc
, argv
, "htpec:d:m:")) != -1) {
323 if (!optarg
|| !*optarg
)
324 dieusage("-c: invalid class name");
328 if (!optarg
|| !*optarg
)
329 dieusage("-c: invalid description");
333 if (!optarg
|| !*optarg
)
334 dieusage("-m: invalid message");
337 escmsg
= xml_escape(optarg
);
341 dieusage("invalid option");
345 if (!classname
|| !desc
)
346 dieusage("-c <class> and -d <desc> are required");
347 if (strlen(classname
) > 64)
348 die("classname must be 64 characters or less in length");
349 if (strlen(desc
) > 512)
350 die("description must be 512 characters or less in length");
354 dieusage("no command specified");
355 args
= (char **)malloc((argc
+ 1) * sizeof(char *));
357 diefailure("malloc failed");
358 memcpy(args
, argv
, argc
* sizeof(char *));
362 diefailure("pipe failed");
363 setcloexec(wakeup_w
);
364 setcloexec(wakeup_r
);
365 setnonblock(wakeup_w
);
366 setnonblock(wakeup_r
);
368 memset(&su
.sun
, 0, sizeof(su
.sun
));
369 su
.sun
.sun_family
= AF_UNIX
;
370 strncpy(su
.sun
.sun_path
, SOCKET_FILE
, sizeof(su
.sun
.sun_path
)-1);
371 fd2
= socket(AF_UNIX
, SOCK_STREAM
, 0);
373 diefailure("socket failed");
374 fd
= fcntl(fd2
, F_DUPFD
, 16);
376 diefailure("fcntl F_DUPFD failed");
378 if (connect(fd
, &su
.sa
, sizeof(su
.sun
))) {
379 if (errno
== ECONNREFUSED
|| errno
== EACCES
||
380 errno
== ENOENT
|| errno
== ENOTDIR
) {
382 if (!ok_connect_fail
)
383 send_failure(10, escmsg
);
387 diefailure("connect failed");
391 signal(SIGPIPE
, sigpipe
);
394 snprintf(request
, sizeof(request
), "throttle %u %s %s\n",
395 (unsigned)getpid(), classname
, desc
);
396 if (writeall(fd
, request
, strlen(request
)) == -1) {
397 if (!ok_request_fail
)
398 send_failure(11, escmsg
);
406 int nfds
= wakeup_r
+ 1;
416 FD_SET(wakeup_r
, &fds
);
418 while (!piped
&& !requesterr
&& !proceed
&& !throttled
) {
423 FD_COPY(&fds
, &rset
);
424 FD_COPY(&fds
, &eset
);
427 select(nfds
, &rset
, NULL
, &eset
, &tv
);
429 readcnt
= read(fd
, request
+ off
, sizeof(request
) - 1 - off
);
432 off
+= (size_t)readcnt
;
434 if ((nl
= strchr(request
, '\n')) != NULL
) {
435 if (!strcmp(request
, "proceed\n"))
437 else if (nl
> request
&& isalphach(request
[0]))
443 } while (readcnt
> 0 && !proceed
&& !throttled
&& !requesterr
);
444 if ((!readcnt
&& !proceed
&& !throttled
) ||
445 (readcnt
== -1 && errno
!= EINTR
&& errno
!= EAGAIN
) ||
446 (off
+ 1 >= sizeof(request
) && !proceed
&& !throttled
)) {
451 if (writeall(fd
, "keepalive\n", 10) == -1)
455 if (requesterr
&& !ok_request_fail
)
456 send_failure(20, escmsg
);
458 send_failure(21, escmsg
);
463 signal(SIGPIPE
, SIG_DFL
);
465 execvp(args
[0], args
);
466 send_failure(3, escmsg
);