]> git.ozlabs.org Git - ccan/blob - tools/ccanlint/async.c
ccanlint: drop -d, interpret any arguments as directories, and allow multiple.
[ccan] / tools / ccanlint / async.c
1 #include "ccanlint.h"
2 #include "../tools.h"
3 #include <sys/types.h>
4 #include <sys/time.h>
5 #include <sys/resource.h>
6 #include <sys/wait.h>
7 #include <sys/stat.h>
8 #include <fcntl.h>
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <err.h>
12 #include <assert.h>
13 #include <ccan/lbalance/lbalance.h>
14 #include <ccan/tlist/tlist.h>
15 #include <ccan/grab_file/grab_file.h>
16 #include <ccan/time/time.h>
17 #include <ccan/talloc/talloc.h>
18
19 static struct lbalance *lb;
20 TLIST_TYPE(command, struct command);
21 static struct tlist_command pending = TLIST_INIT(pending);
22 static struct tlist_command running = TLIST_INIT(running);
23 static unsigned int num_running = 0;
24 static struct tlist_command done = TLIST_INIT(done);
25
26 struct command {
27         struct list_node list;
28         char *command;
29         pid_t pid;
30         int output_fd;
31         unsigned int time_ms;
32         struct lbalance_task *task;
33         int status;
34         char *output;
35         bool done;
36         const void *ctx;
37 };
38
39 static void killme(int sig)
40 {
41         kill(-getpid(), SIGKILL);
42 }
43
44 static void run_more(void)
45 {
46         struct command *c;
47
48         while (num_running < lbalance_target(lb)) {
49                 int p[2];
50
51                 c = tlist_top(&pending, struct command, list);
52                 if (!c)
53                         break;
54
55                 fflush(stdout);
56                 if (pipe(p) != 0)
57                         err(1, "Pipe failed");
58                 c->pid = fork();
59                 if (c->pid == -1)
60                         err(1, "Fork failed");
61                 if (c->pid == 0) {
62                         struct itimerval itim;
63
64                         if (dup2(p[1], STDOUT_FILENO) != STDOUT_FILENO
65                             || dup2(p[1], STDERR_FILENO) != STDERR_FILENO
66                             || close(p[0]) != 0
67                             || close(STDIN_FILENO) != 0
68                             || open("/dev/null", O_RDONLY) != STDIN_FILENO)
69                                 exit(128);
70
71                         signal(SIGALRM, killme);
72                         itim.it_interval.tv_sec = itim.it_interval.tv_usec = 0;
73                         itim.it_value = time_from_msec(c->time_ms);
74                         setitimer(ITIMER_REAL, &itim, NULL);
75
76                         c->status = system(c->command);
77                         if (WIFEXITED(c->status))
78                                 exit(WEXITSTATUS(c->status));
79                         /* Here's a hint... */
80                         exit(128 + WTERMSIG(c->status));
81                 }
82
83                 if (tools_verbose)
84                         printf("Running async: %s => %i\n", c->command, c->pid);
85
86                 close(p[1]);
87                 c->output_fd = p[0];
88                 c->task = lbalance_task_new(lb);
89                 tlist_del_from(&pending, c, list);
90                 tlist_add_tail(&running, c, list);
91                 num_running++;
92         }
93 }
94
95 static int destroy_command(struct command *command)
96 {
97         if (!command->done && command->pid) {
98                 kill(-command->pid, SIGKILL);
99                 close(command->output_fd);
100                 num_running--;
101         }
102
103         tlist_del(command, list);
104         return 0;
105 }
106
107 void run_command_async(const void *ctx, unsigned int time_ms,
108                        const char *fmt, ...)
109 {
110         struct command *command;
111         va_list ap;
112
113         assert(ctx);
114
115         if (!lb)
116                 lb = lbalance_new();
117
118         command = talloc(ctx, struct command);
119         command->ctx = ctx;
120         command->time_ms = time_ms;
121         command->pid = 0;
122         command->output = talloc_strdup(command, "");
123         va_start(ap, fmt);
124         command->command = talloc_vasprintf(command, fmt, ap);
125         va_end(ap);
126         tlist_add_tail(&pending, command, list);
127         command->done = false;
128         talloc_set_destructor(command, destroy_command);
129
130         run_more();
131 }
132
133 static void reap_output(void)
134 {
135         fd_set in;
136         struct command *c, *next;
137         int max_fd = 0;
138
139         FD_ZERO(&in);
140
141         tlist_for_each(&running, c, list) {
142                 FD_SET(c->output_fd, &in);
143                 if (c->output_fd > max_fd)
144                         max_fd = c->output_fd;
145         }
146
147         if (select(max_fd+1, &in, NULL, NULL, NULL) < 0)
148                 err(1, "select failed");
149
150         tlist_for_each_safe(&running, c, next, list) {
151                 if (FD_ISSET(c->output_fd, &in)) {
152                         int old_len, len;
153                         /* This length includes nul terminator! */
154                         old_len = talloc_array_length(c->output);
155                         c->output = talloc_realloc(c, c->output, char,
156                                                    old_len + 1024);
157                         len = read(c->output_fd, c->output + old_len - 1, 1024);
158                         if (len < 0)
159                                 err(1, "Reading from async command");
160                         c->output = talloc_realloc(c, c->output, char,
161                                                    old_len + len);
162                         c->output[old_len + len - 1] = '\0';
163                         if (len == 0) {
164                                 struct rusage ru;
165                                 wait4(c->pid, &c->status, 0, &ru);
166                                 if (tools_verbose)
167                                         printf("Finished async %i: %s %u\n",
168                                                c->pid,
169                                                WIFEXITED(c->status)
170                                                ? "exit status"
171                                                : "killed by signal",
172                                                WIFEXITED(c->status)
173                                                ? WEXITSTATUS(c->status)
174                                                : WTERMSIG(c->status));
175                                 lbalance_task_free(c->task, &ru);
176                                 c->task = NULL;
177                                 c->done = true;
178                                 close(c->output_fd);
179                                 tlist_del_from(&running, c, list);
180                                 tlist_add_tail(&done, c, list);
181                                 num_running--;
182                         }
183                 }
184         }
185 }
186
187 void *collect_command(bool *ok, char **output)
188 {
189         struct command *c;
190         const void *ctx;
191
192         while ((c = tlist_top(&done, struct command, list)) == NULL) {
193                 if (tlist_empty(&pending) && tlist_empty(&running))
194                         return NULL;
195                 reap_output();
196                 run_more();
197         }
198
199         *ok = (WIFEXITED(c->status) && WEXITSTATUS(c->status) == 0);
200         ctx = c->ctx;
201         *output = talloc_steal(ctx, c->output);
202         talloc_free(c);
203         return (void *)ctx;
204 }
205
206 /* Compile and link single C file, with object files, async. */
207 void compile_and_link_async(const void *ctx, unsigned int time_ms,
208                             const char *cfile, const char *ccandir,
209                             const char *objs, const char *compiler,
210                             const char *cflags,
211                             const char *libs, const char *outfile)
212 {
213         if (compile_verbose)
214                 printf("Compiling and linking (async) %s\n", outfile);
215         run_command_async(ctx, time_ms,
216                           "%s %s -I%s -o %s %s %s %s",
217                           compiler, cflags,
218                           ccandir, outfile, cfile, objs, libs);
219 }