ccanlint: chdir to temporary dir so gcov files land there.
[ccan] / tools / tools.c
1 #include <ccan/talloc/talloc.h>
2 #include <ccan/grab_file/grab_file.h>
3 #include <ccan/noerr/noerr.h>
4 #include <ccan/read_write_all/read_write_all.h>
5 #include <ccan/noerr/noerr.h>
6 #include <sys/stat.h>
7 #include <sys/types.h>
8 #include <sys/time.h>
9 #include <sys/wait.h>
10 #include <fcntl.h>
11 #include <string.h>
12 #include <unistd.h>
13 #include <stdarg.h>
14 #include <errno.h>
15 #include <err.h>
16 #include <unistd.h>
17 #include "tools.h"
18
19 static char *tmpdir = NULL;
20 static unsigned int count;
21
22 /* Ten minutes. */
23 const unsigned int default_timeout_ms = 10 * 60 * 1000;
24
25 char *talloc_basename(const void *ctx, const char *dir)
26 {
27         char *p = strrchr(dir, '/');
28
29         if (!p)
30                 return (char *)dir;
31         return talloc_strdup(ctx, p+1);
32 }
33
34 char *talloc_dirname(const void *ctx, const char *dir)
35 {
36         char *p = strrchr(dir, '/');
37
38         if (!p)
39                 return talloc_strdup(ctx, ".");
40         return talloc_strndup(ctx, dir, p - dir);
41 }
42
43 char *talloc_getcwd(const void *ctx)
44 {
45         unsigned int len;
46         char *cwd;
47
48         /* *This* is why people hate C. */
49         len = 32;
50         cwd = talloc_array(ctx, char, len);
51         while (!getcwd(cwd, len)) {
52                 if (errno != ERANGE) {
53                         talloc_free(cwd);
54                         return NULL;
55                 }
56                 cwd = talloc_realloc(ctx, cwd, char, len *= 2);
57         }
58         return cwd;
59 }
60
61 static void killme(int sig)
62 {
63         kill(-getpid(), SIGKILL);
64 }
65
66 char *run_with_timeout(const void *ctx, const char *cmd,
67                        bool *ok, unsigned *timeout_ms)
68 {
69         pid_t pid;
70         int p[2];
71         char *ret;
72         int status, ms;
73         struct timeval start, end;
74
75         *ok = false;
76         if (pipe(p) != 0)
77                 return talloc_asprintf(ctx, "Failed to create pipe: %s",
78                                        strerror(errno));
79
80         gettimeofday(&start, NULL);
81         pid = fork();
82         if (pid == -1) {
83                 close_noerr(p[0]);
84                 close_noerr(p[1]);
85                 return talloc_asprintf(ctx, "Failed to fork: %s",
86                                        strerror(errno));
87                 return NULL;
88         }
89
90         if (pid == 0) {
91                 struct itimerval itim;
92
93                 if (dup2(p[1], STDOUT_FILENO) != STDOUT_FILENO
94                     || dup2(p[1], STDERR_FILENO) != STDERR_FILENO
95                     || close(p[0]) != 0
96                     || close(STDIN_FILENO) != 0
97                     || open("/dev/null", O_RDONLY) != STDIN_FILENO)
98                         exit(128);
99
100                 signal(SIGALRM, killme);
101                 itim.it_interval.tv_sec = itim.it_interval.tv_usec = 0;
102                 itim.it_value.tv_sec = *timeout_ms / 1000;
103                 itim.it_value.tv_usec = (*timeout_ms % 1000) * 1000;
104                 setitimer(ITIMER_REAL, &itim, NULL);
105
106                 status = system(cmd);
107                 if (WIFEXITED(status))
108                         exit(WEXITSTATUS(status));
109                 /* Here's a hint... */
110                 exit(128 + WTERMSIG(status));
111         }
112
113         close(p[1]);
114         ret = grab_fd(ctx, p[0], NULL);
115         /* This shouldn't fail... */
116         if (waitpid(pid, &status, 0) != pid)
117                 err(1, "Failed to wait for child");
118
119         gettimeofday(&end, NULL);
120         if (end.tv_usec < start.tv_usec) {
121                 end.tv_usec += 1000000;
122                 end.tv_sec--;
123         }
124         ms = (end.tv_sec - start.tv_sec) * 1000
125                 + (end.tv_usec - start.tv_usec) / 1000;
126         if (ms > *timeout_ms)
127                 *timeout_ms = 0;
128         else
129                 *timeout_ms -= ms;
130
131         *ok = (WIFEXITED(status) && WEXITSTATUS(status) == 0);
132         return ret;
133 }
134
135 /* Returns output if command fails. */
136 char *run_command(const void *ctx, unsigned int *time_ms, const char *fmt, ...)
137 {
138         va_list ap;
139         char *cmd, *contents;
140         bool ok;
141         unsigned int default_time = default_timeout_ms;
142
143         if (!time_ms)
144                 time_ms = &default_time;
145         else if (*time_ms == 0)
146                 return talloc_strdup(ctx, "\n== TIMED OUT ==\n");
147
148         va_start(ap, fmt);
149         cmd = talloc_vasprintf(ctx, fmt, ap);
150         va_end(ap);
151
152         contents = run_with_timeout(ctx, cmd, &ok, time_ms);
153         if (ok) {
154                 talloc_free(contents);
155                 return NULL;
156         }
157
158         if (!contents)
159                 err(1, "Problem running child");
160         if (*time_ms == 0)
161                 contents = talloc_asprintf_append(contents,
162                                                   "\n== TIMED OUT ==\n");
163         return contents;
164 }
165
166 static int unlink_all(char *dir)
167 {
168         char cmd[strlen(dir) + sizeof("rm -rf ")];
169         sprintf(cmd, "rm -rf %s", dir);
170         if (system(cmd) != 0)
171                 warn("Could not remove temporary work in %s", dir);
172         return 0;
173 }
174
175 char *temp_dir(const void *ctx)
176 {
177         /* For first call, create dir. */
178         while (!tmpdir) {
179                 tmpdir = getenv("TMPDIR");
180                 if (!tmpdir)
181                         tmpdir = "/tmp";
182                 tmpdir = talloc_asprintf(talloc_autofree_context(),
183                                          "%s/ccanlint-%u.%lu",
184                                          tmpdir, getpid(), random());
185                 if (mkdir(tmpdir, 0700) != 0) {
186                         if (errno == EEXIST) {
187                                 talloc_free(tmpdir);
188                                 tmpdir = NULL;
189                                 continue;
190                         }
191                         err(1, "mkdir %s failed", tmpdir);
192                 }
193                 talloc_set_destructor(tmpdir, unlink_all);
194         }
195         return tmpdir;
196 }
197
198 char *temp_file(const void *ctx, const char *extension)
199 {
200         return talloc_asprintf(ctx, "%s/%u%s",
201                                temp_dir(ctx), count++, extension);
202 }
203
204 char *maybe_temp_file(const void *ctx, const char *extension, bool keep,
205                       const char *srcname)
206 {
207         size_t baselen;
208
209         if (!keep)
210                 return temp_file(ctx, extension);
211
212         baselen = strrchr(srcname, '.') - srcname;
213         return talloc_asprintf(ctx, "%.*s%s", baselen, srcname, extension);
214 }
215
216 bool move_file(const char *oldname, const char *newname)
217 {
218         char *contents;
219         size_t size;
220         int fd;
221         bool ret;
222
223         /* Simple case: rename works. */
224         if (rename(oldname, newname) == 0)
225                 return true;
226
227         /* Try copy and delete: not atomic! */
228         contents = grab_file(NULL, oldname, &size);
229         if (!contents)
230                 return false;
231
232         fd = open(newname, O_WRONLY|O_CREAT|O_TRUNC, 0666);
233         if (fd < 0) {
234                 ret = false;
235                 goto free;
236         }
237
238         ret = write_all(fd, contents, size);
239         if (close(fd) != 0)
240                 ret = false;
241
242         if (ret)
243                 unlink(oldname);
244         else
245                 unlink(newname);
246
247 free:
248         talloc_free(contents);
249         return ret;
250 }