2 Copyright (C) 2012,2014,2016 rofl0r
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #define VERSION "1.1.1"
22 #undef _POSIX_C_SOURCE
23 #define _POSIX_C_SOURCE 200809L
25 #define _XOPEN_SOURCE 700
29 #include "../lib/include/optparser.h"
30 #include "../lib/include/stringptr.h"
31 #include "../lib/include/stringptrlist.h"
32 #include "../lib/include/sblist.h"
33 #include "../lib/include/strlib.h"
34 #include "../lib/include/timelib.h"
35 #include "../lib/include/filelib.h"
47 /* process handling */
54 #include <sys/resource.h>
56 #if defined(__GLIBC__) && (__GLIBC__ < 3) && (__GLIBC_MINOR__ < 13)
57 /* http://repo.or.cz/w/glibc.git/commitdiff/c08fb0d7bba4015078406b28d3906ccc5fda9d5a ,
58 * http://repo.or.cz/w/glibc.git/commitdiff/052fa7b33ef5deeb4987e5264cf397b3161d8a01 */
59 #warning to use prlimit() you have to use musl libc 0.8.4+ or glibc 2.13+
60 static int prlimit(int pid
, ...) {
62 dprintf(2, "prlimit() not implemented on this system\n");
74 posix_spawn_file_actions_t fa
;
85 unsigned long long lineno
;
87 unsigned threads_running
;
89 unsigned long long skip
;
91 sblist
* subst_entries
;
93 unsigned cmd_startarg
;
95 int delayedspinup_interval
; /* use a random delay until the queue gets filled for the first time.
96 the top value in ms can be supplied via a command line switch.
97 this option makes only sense if the interval is somewhat smaller than the
98 expected runtime of the average job.
99 this option is useful to not overload a network app due to hundreds of
100 parallel connection tries on startup.
102 int buffered
:1; /* write stdout and stderr of each task into a file,
103 and print it to stdout once the process ends.
104 this prevents mixing up of the output of multiple tasks. */
105 int delayedflush
:1; /* only write to statefile whenever all processes are busy, and at program end.
106 this means faster program execution, but could also be imprecise if the number of
107 jobs is small or smaller than the available threadcount. */
108 int join_output
:1; /* join stdout and stderr of launched jobs into stdout */
113 prog_state_s prog_state
;
116 extern char** environ
;
118 int makeLogfilename(char* buf
, size_t bufsize
, size_t jobindex
, int is_stderr
) {
119 int ret
= snprintf(buf
, bufsize
, "%s/jd_proc_%.5lu_std%s.log",
120 prog_state
.tempdir
, (unsigned long) jobindex
, is_stderr
? "err" : "out");
121 return ret
> 0 && (size_t) ret
< bufsize
;
124 void launch_job(size_t jobindex
, char** argv
) {
125 char stdout_filename_buf
[256];
126 char stderr_filename_buf
[256];
127 job_info
* job
= sblist_get(prog_state
.job_infos
, jobindex
);
129 if(job
->pid
!= -1) return;
131 if(prog_state
.buffered
) {
132 if((!makeLogfilename(stdout_filename_buf
, sizeof(stdout_filename_buf
), jobindex
, 0)) ||
133 ((!prog_state
.join_output
) && !makeLogfilename(stderr_filename_buf
, sizeof(stderr_filename_buf
), jobindex
, 1)) ) {
134 dprintf(2, "temp filename too long!\n");
139 errno
= posix_spawn_file_actions_init(&job
->fa
);
140 if(errno
) goto spawn_error
;
142 errno
= posix_spawn_file_actions_addclose(&job
->fa
, 0);
143 if(errno
) goto spawn_error
;
146 if(prog_state
.pipe_mode
) {
151 job
->pipe
= pipes
[1];
152 errno
= posix_spawn_file_actions_adddup2(&job
->fa
, pipes
[0], 0);
153 if(errno
) goto spawn_error
;
154 errno
= posix_spawn_file_actions_addclose(&job
->fa
, pipes
[0]);
155 if(errno
) goto spawn_error
;
156 errno
= posix_spawn_file_actions_addclose(&job
->fa
, pipes
[1]);
157 if(errno
) goto spawn_error
;
160 if(prog_state
.buffered
) {
161 errno
= posix_spawn_file_actions_addclose(&job
->fa
, 1);
162 if(errno
) goto spawn_error
;
163 errno
= posix_spawn_file_actions_addclose(&job
->fa
, 2);
164 if(errno
) goto spawn_error
;
167 if(!prog_state
.pipe_mode
) {
168 errno
= posix_spawn_file_actions_addopen(&job
->fa
, 0, "/dev/null", O_RDONLY
, 0);
169 if(errno
) goto spawn_error
;
172 if(prog_state
.buffered
) {
173 errno
= posix_spawn_file_actions_addopen(&job
->fa
, 1, stdout_filename_buf
, O_WRONLY
| O_CREAT
| O_TRUNC
, S_IRUSR
| S_IWUSR
| S_IRGRP
| S_IROTH
);
174 if(errno
) goto spawn_error
;
175 if(prog_state
.join_output
)
176 errno
= posix_spawn_file_actions_adddup2(&job
->fa
, 1, 2);
178 errno
= posix_spawn_file_actions_addopen(&job
->fa
, 2, stderr_filename_buf
, O_WRONLY
| O_CREAT
| O_TRUNC
, S_IRUSR
| S_IWUSR
| S_IRGRP
| S_IROTH
);
179 if(errno
) goto spawn_error
;
182 errno
= posix_spawnp(&job
->pid
, argv
[0], &job
->fa
, NULL
, argv
, environ
);
186 perror("posix_spawn");
188 prog_state
.threads_running
++;
189 if(prog_state
.limits
) {
191 sblist_iter(prog_state
.limits
, limit
) {
192 if(prlimit(job
->pid
, limit
->limit
, &limit
->rl
, NULL
) == -1)
197 if(prog_state
.pipe_mode
)
201 static void dump_output(size_t job_id
, int is_stderr
) {
202 char out_filename_buf
[256];
204 FILE* dst
, *out_stream
= is_stderr
? stderr
: stdout
;
207 makeLogfilename(out_filename_buf
, sizeof(out_filename_buf
), job_id
, is_stderr
);
209 dst
= fopen(out_filename_buf
, "r");
211 while((nread
= fread(buf
, 1, sizeof(buf
), dst
))) {
212 fwrite(buf
, 1, nread
, out_stream
);
213 if(nread
< sizeof(buf
)) break;
217 unlink(out_filename_buf
);
221 static void write_all(int fd
, void* buf
, size_t size
) {
226 ssize_t n
= write(fd
, p
, left
);
229 if(errno
== EINTR
) continue;
241 static void pass_stdin(stringptr
*line
) {
242 static size_t next_child
= 0;
243 if(next_child
>= sblist_getsize(prog_state
.job_infos
))
245 job_info
*job
= sblist_get(prog_state
.job_infos
, next_child
);
246 write_all(job
->pipe
, line
->ptr
, line
->size
);
250 static void close_pipes(void) {
252 for(i
= 0; i
< sblist_getsize(prog_state
.job_infos
); i
++) {
253 job_info
*job
= sblist_get(prog_state
.job_infos
, i
);
258 /* wait till a child exits, reap it, and return its job index for slot reuse */
259 static size_t reap_child(void) {
264 do ret
= waitpid(-1, &retval
, 0);
265 while(ret
== -1 || !WIFEXITED(retval
));
267 for(i
= 0; i
< sblist_getsize(prog_state
.job_infos
); i
++) {
268 job
= sblist_get(prog_state
.job_infos
, i
);
269 if(job
->pid
== ret
) {
271 posix_spawn_file_actions_destroy(&job
->fa
);
272 prog_state
.threads_running
--;
273 if(prog_state
.buffered
) {
275 if(!prog_state
.join_output
)
285 static size_t free_slots(void) {
286 return prog_state
.numthreads
- prog_state
.threads_running
;
289 __attribute__((noreturn
))
290 static void die(const char* msg
) {
295 static unsigned long parse_human_number(stringptr
* num
) {
296 unsigned long ret
= 0;
297 static const unsigned long mul
[] = {1024, 1024 * 1024, 1024 * 1024 * 1024};
298 const char* kmg
= "KMG";
300 if(num
&& num
->size
) {
301 ret
= atol(num
->ptr
);
302 if((kmgind
= strchr(kmg
, num
->ptr
[num
->size
-1])))
303 ret
*= mul
[kmgind
- kmg
];
308 static int syntax(void) {
310 "jobflow " VERSION
" (C) rofl0r\n"
311 "------------------\n"
312 "this program is intended to be used as a recipient of another programs output\n"
313 "it launches processes to which the current line can be passed as an argument\n"
314 "using {} for substitution (as in find -exec).\n"
315 "if no substitution argument ({} or {.}) is provided, input is piped into\n"
316 "stdin of child processes. input will be then evenly distributed to jobs,\n"
317 "until EOF is received.\n"
319 "available options:\n\n"
320 "-skip=XXX -threads=XXX -resume -statefile=/tmp/state -delayedflush\n"
321 "-delayedspinup=XXX -buffered -joinoutput -limits=mem=16M,cpu=10\n"
322 "-exec ./mycommand {}\n"
325 " XXX=number of entries to skip\n"
327 " XXX=number of parallel processes to spawn\n"
329 " resume from last jobnumber stored in statefile\n"
332 " saves last launched jobnumber into a file\n"
334 " only write to statefile whenever all processes are busy,\n"
335 " and at program end\n"
336 "-delayedspinup=XXX\n"
337 " XXX=maximum amount of milliseconds\n"
338 " ...to wait when spinning up a fresh set of processes\n"
339 " a random value between 0 and the chosen amount is used to delay initial\n"
341 " this can be handy to circumvent an I/O lockdown because of a burst of \n"
342 " activity on program startup\n"
344 " store the stdout and stderr of launched processes into a temporary file\n"
345 " which will be printed after a process has finished.\n"
346 " this prevents mixing up of output of different processes.\n"
348 " if -buffered, write both stdout and stderr into the same file.\n"
349 " this saves the chronological order of the output, and the combined output\n"
350 " will only be printed to stdout.\n"
352 " do bulk copies with a buffer of XXX bytes. only usable in pipe mode.\n"
353 " this passes (almost) the entire buffer to the next scheduled job.\n"
354 " the passed buffer will be truncated to the last line break boundary,\n"
355 " so jobs always get entire lines to work with.\n"
356 " this option is useful when you have huge input files and relatively short\n"
357 " task runtimes. by using it, syscall overhead can be reduced to a minimum.\n"
358 " XXX must be a multiple of 4KB. the suffixes G/M/K are detected.\n"
359 " actual memory allocation will be twice the amount passed.\n"
360 " note that pipe buffer size is limited to 64K on linux, so anything higher\n"
361 " than that probably doesn't make sense.\n"
362 " if no size is passed (i.e. only -bulk), a default of 4K will be used.\n"
363 "-limits=[mem=XXX,cpu=XXX,stack=XXX,fsize=XXX,nofiles=XXX]\n"
364 " sets the rlimit of the new created processes.\n"
365 " see \"man setrlimit\" for an explanation. the suffixes G/M/K are detected.\n"
366 "-exec command with args\n"
367 " everything past -exec is treated as the command to execute on each line of\n"
368 " stdin received. the line can be passed as an argument using {}.\n"
369 " {.} passes everything before the last dot in a line as an argument.\n"
370 " it is possible to use multiple substitutions inside a single argument,\n"
371 " but currently only of one type.\n"
372 " if -exec is omitted, input will merely be dumped to stdout (like cat).\n"
379 #define strtoll(a,b,c) strtoint64(a, strlen(a))
380 static int parse_args(int argc
, char** argv
) {
381 op_state op_b
, *op
= &op_b
;
382 op_init(op
, argc
, argv
);
384 if(op_hasflag(op
, SPL("-help")))
387 op_temp
= op_get(op
, SPL("threads"));
388 long long x
= op_temp
? strtoll(op_temp
,0,10) : 1;
389 if(x
<= 0) die("threadcount must be >= 1\n");
390 prog_state
.numthreads
= x
;
392 op_temp
= op_get(op
, SPL("statefile"));
393 prog_state
.statefile
= op_temp
;
395 op_temp
= op_get(op
, SPL("skip"));
396 prog_state
.skip
= op_temp
? strtoll(op_temp
,0,10) : 0;
397 if(op_hasflag(op
, SPL("resume"))) {
398 if(!prog_state
.statefile
) die("-resume needs -statefile\n");
399 if(access(prog_state
.statefile
, W_OK
| R_OK
) != -1) {
400 FILE *f
= fopen(prog_state
.statefile
, "r");
403 if(fgets(nb
, sizeof nb
, f
)) prog_state
.skip
= strtoll(nb
,0,10);
409 prog_state
.delayedflush
= 0;
410 if(op_hasflag(op
, SPL("delayedflush"))) {
411 if(!prog_state
.statefile
) die("-delayedflush needs -statefile\n");
412 prog_state
.delayedflush
= 1;
415 prog_state
.pipe_mode
= 0;
417 op_temp
= op_get(op
, SPL("delayedspinup"));
418 prog_state
.delayedspinup_interval
= op_temp
? strtoll(op_temp
,0,10) : 0;
420 prog_state
.cmd_startarg
= 0;
421 prog_state
.subst_entries
= NULL
;
423 if(op_hasflag(op
, SPL("exec"))) {
426 for(i
= 1; i
< (unsigned) argc
; i
++) {
427 if(str_equal(argv
[i
], "-exec")) {
432 if(r
&& r
< (unsigned) argc
) {
433 prog_state
.cmd_startarg
= r
;
436 prog_state
.subst_entries
= sblist_new(sizeof(uint32_t), 16);
438 // save entries which must be substituted, to save some cycles.
439 for(i
= r
; i
< (unsigned) argc
; i
++) {
441 if(strstr(argv
[i
], "{}") || strstr(argv
[i
], "{.}")) {
442 sblist_add(prog_state
.subst_entries
, &subst_ent
);
445 if(sblist_getsize(prog_state
.subst_entries
) == 0) {
446 prog_state
.pipe_mode
= 1;
447 sblist_free(prog_state
.subst_entries
);
448 prog_state
.subst_entries
= 0;
452 prog_state
.buffered
= 0;
453 if(op_hasflag(op
, SPL("buffered"))) {
454 prog_state
.buffered
= 1;
457 prog_state
.join_output
= 0;
458 if(op_hasflag(op
, SPL("joinoutput"))) {
459 if(!prog_state
.buffered
) die("-joinoutput needs -buffered\n");
460 prog_state
.join_output
= 1;
463 prog_state
.bulk_bytes
= 0;
464 op_temp
= op_get(op
, SPL("bulk"));
466 SPDECLAREC(value
, op_temp
);
467 prog_state
.bulk_bytes
= parse_human_number(value
);
468 if(prog_state
.bulk_bytes
% 4096)
469 die("bulk size must be a multiple of 4096\n");
470 } else if(op_hasflag(op
, SPL("bulk")))
471 prog_state
.bulk_bytes
= 4096;
473 prog_state
.limits
= NULL
;
474 op_temp
= op_get(op
, SPL("limits"));
477 SPDECLAREC(limits
, op_temp
);
478 stringptrlist
* limit_list
= stringptr_splitc(limits
, ',');
480 stringptr
* key
, *value
;
482 if(stringptrlist_getsize(limit_list
)) {
483 prog_state
.limits
= sblist_new(sizeof(limit_rec
), stringptrlist_getsize(limit_list
));
484 for(i
= 0; i
< stringptrlist_getsize(limit_list
); i
++) {
485 kv
= stringptr_splitc(stringptrlist_get(limit_list
, i
), '=');
486 if(stringptrlist_getsize(kv
) != 2) continue;
487 key
= stringptrlist_get(kv
, 0);
488 value
= stringptrlist_get(kv
, 1);
489 if(EQ(key
, SPL("mem")))
490 lim
.limit
= RLIMIT_AS
;
491 else if(EQ(key
, SPL("cpu")))
492 lim
.limit
= RLIMIT_CPU
;
493 else if(EQ(key
, SPL("stack")))
494 lim
.limit
= RLIMIT_STACK
;
495 else if(EQ(key
, SPL("fsize")))
496 lim
.limit
= RLIMIT_FSIZE
;
497 else if(EQ(key
, SPL("nofiles")))
498 lim
.limit
= RLIMIT_NOFILE
;
500 die("unknown option passed to -limits");
502 if(getrlimit(lim
.limit
, &lim
.rl
) == -1) {
504 die("could not query rlimits");
506 lim
.rl
.rlim_cur
= parse_human_number(value
);
507 sblist_add(prog_state
.limits
, &lim
);
508 stringptrlist_free(kv
);
510 stringptrlist_free(limit_list
);
516 static void init_queue(void) {
518 job_info ji
= {.pid
= -1};
520 for(i
= 0; i
< prog_state
.numthreads
; i
++)
521 sblist_add(prog_state
.job_infos
, &ji
);
524 static void write_statefile(unsigned long long n
, const char* tempfile
) {
525 int fd
= open(tempfile
, O_WRONLY
| O_CREAT
| O_TRUNC
, S_IRUSR
| S_IWUSR
| S_IRGRP
| S_IROTH
);
527 dprintf(fd
, "%llu\n", n
+ 1ULL);
529 if(rename(tempfile
, prog_state
.statefile
) == -1)
535 // returns numbers of substitutions done, -1 on out of buffer.
536 // dest is always overwritten. if not substitutions were done, it contains a copy of source.
537 int substitute_all(char* dest
, ssize_t dest_size
, stringptr
* source
, stringptr
* what
, stringptr
* whit
) {
540 for(i
= 0; dest_size
> 0 && i
< source
->size
; ) {
541 if(stringptr_here(source
, i
, what
)) {
542 if(dest_size
< (ssize_t
) whit
->size
) return -1;
543 memcpy(dest
, whit
->ptr
, whit
->size
);
545 dest_size
-= whit
->size
;
549 *dest
= source
->ptr
[i
];
555 if(!dest_size
) return -1;
560 static char* mystrnchr(const char *in
, int ch
, size_t end
) {
561 const char *e
= in
+end
;
563 while(p
!= e
&& *p
!= ch
) p
++;
564 if(*p
== ch
) return (char*)p
;
567 static char* mystrnrchr(const char *in
, int ch
, size_t end
) {
568 const char *e
= in
+end
-1;
570 while(p
!= e
&& *e
!= ch
) e
--;
571 if(*e
== ch
) return (char*)e
;
575 static int need_linecounter(void) {
576 return !!prog_state
.skip
|| prog_state
.statefile
;
578 static size_t count_linefeeds(const char *buf
, size_t len
) {
579 const char *p
= buf
, *e
= buf
+len
;
582 if(*p
== '\n') cnt
++;
587 static int dispatch_line(char* inbuf
, size_t len
, char** argv
) {
588 char subst_buf
[16][4096];
589 static unsigned spinup_counter
= 0;
591 stringptr line_b
, *line
= &line_b
;
593 if(!prog_state
.bulk_bytes
)
595 else if(need_linecounter()) {
596 prog_state
.lineno
+= count_linefeeds(inbuf
, len
);
599 if(prog_state
.skip
) {
600 if(!prog_state
.bulk_bytes
) {
604 while(len
&& prog_state
.skip
) {
605 char *q
= mystrnchr(inbuf
, '\n', len
);
607 ptrdiff_t diff
= (q
- inbuf
) + 1;
618 if(!prog_state
.cmd_startarg
) {
619 write_all(1, inbuf
, len
);
623 line
->ptr
= inbuf
; line
->size
= len
;
625 if(!prog_state
.pipe_mode
)
626 stringptr_chomp(line
);
628 if(prog_state
.subst_entries
) {
629 unsigned max_subst
= 0;
631 sblist_iter(prog_state
.subst_entries
, index
) {
632 SPDECLAREC(source
, argv
[*index
+ prog_state
.cmd_startarg
]);
634 ret
= substitute_all(subst_buf
[max_subst
], 4096, source
, SPL("{}"), line
);
637 dprintf(2, "fatal: line too long for substitution: %s\n", line
->ptr
);
640 char* lastdot
= stringptr_rchr(line
, '.');
641 stringptr tilLastDot
= *line
;
642 if(lastdot
) tilLastDot
.size
= lastdot
- line
->ptr
;
643 ret
= substitute_all(subst_buf
[max_subst
], 4096, source
, SPL("{.}"), &tilLastDot
);
644 if(ret
== -1) goto too_long
;
647 prog_state
.cmd_argv
[*index
] = subst_buf
[max_subst
];
654 if(prog_state
.delayedspinup_interval
&& spinup_counter
< (prog_state
.numthreads
* 2)) {
655 msleep(rand() % (prog_state
.delayedspinup_interval
+ 1));
660 launch_job(prog_state
.threads_running
, prog_state
.cmd_argv
);
661 else if(!prog_state
.pipe_mode
)
662 launch_job(reap_child(), prog_state
.cmd_argv
);
664 if(prog_state
.statefile
&& (prog_state
.delayedflush
== 0 || free_slots() == 0)) {
665 write_statefile(prog_state
.lineno
, prog_state
.temp_state
);
668 if(prog_state
.pipe_mode
)
674 int main(int argc
, char** argv
) {
677 char tempdir_buf
[256];
681 if(argc
> 4096) argc
= 4096;
683 prog_state
.threads_running
= 0;
685 if(parse_args(argc
, argv
)) return 1;
687 if(prog_state
.statefile
)
688 snprintf(prog_state
.temp_state
, sizeof(prog_state
.temp_state
), "%s.%u", prog_state
.statefile
, (unsigned) getpid());
690 prog_state
.tempdir
= NULL
;
692 if(prog_state
.buffered
) {
693 prog_state
.tempdir
= tempdir_buf
;
694 if(mktempdir("jobflow", tempdir_buf
, sizeof(tempdir_buf
)) == 0) {
696 die("could not create tempdir\n");
699 /* if the stdout/stderr fds are not in O_APPEND mode,
700 the dup()'s of the fds in posix_spawn can cause different
701 file positions, causing the different processes to overwrite each others output.
703 seq 100 | ./jobflow.out -threads=100 -exec echo {} > test.tmp ; wc -l test.tmp
705 if(fcntl(1, F_SETFL
, O_APPEND
) == -1) perror("fcntl");
706 if(fcntl(2, F_SETFL
, O_APPEND
) == -1) perror("fcntl");
709 if(prog_state
.cmd_startarg
) {
710 for(i
= prog_state
.cmd_startarg
; i
< (unsigned) argc
; i
++) {
711 prog_state
.cmd_argv
[i
- prog_state
.cmd_startarg
] = argv
[i
];
713 prog_state
.cmd_argv
[argc
- prog_state
.cmd_startarg
] = NULL
;
716 prog_state
.job_infos
= sblist_new(sizeof(job_info
), prog_state
.numthreads
);
719 prog_state
.lineno
= 0;
722 const size_t chunksize
= prog_state
.bulk_bytes
? prog_state
.bulk_bytes
: 16*1024;
724 char *mem
= mmap(NULL
, chunksize
*2, PROT_READ
| PROT_WRITE
, MAP_PRIVATE
| MAP_ANON
, -1, 0);
726 char *buf2
= mem
+chunksize
;
732 inbuf
= buf1
+chunksize
-left
;
733 memcpy(inbuf
, buf2
+chunksize
-left
, left
);
734 ssize_t n
= read(0, buf2
, chunksize
);
743 if(prog_state
.pipe_mode
&& prog_state
.bulk_bytes
)
744 p
= mystrnrchr(in
, '\n', left
);
746 p
= mystrnchr (in
, '\n', left
);
749 ptrdiff_t diff
= (p
- in
) + 1;
750 if(!dispatch_line(in
, diff
, argv
))
756 if(left
) dispatch_line(in
, left
, argv
);
759 if(left
> chunksize
) {
760 dprintf(2, "error: input line length exceeds buffer size\n");
769 if(prog_state
.pipe_mode
) {
773 if(prog_state
.delayedflush
)
774 write_statefile(prog_state
.lineno
- 1, prog_state
.temp_state
);
776 while(prog_state
.threads_running
) reap_child();
778 if(prog_state
.subst_entries
) sblist_free(prog_state
.subst_entries
);
779 if(prog_state
.job_infos
) sblist_free(prog_state
.job_infos
);
780 if(prog_state
.limits
) sblist_free(prog_state
.limits
);
782 if(prog_state
.tempdir
)
783 rmdir(prog_state
.tempdir
);