##// END OF EJS Templates
chg: use color in debug/error messages conditionally...
Jun Wu -
r28787:ea86cdcd default
parent child Browse files
Show More
@@ -1,574 +1,577
1 1 /*
2 2 * A fast client for Mercurial command server
3 3 *
4 4 * Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org>
5 5 *
6 6 * This software may be used and distributed according to the terms of the
7 7 * GNU General Public License version 2 or any later version.
8 8 */
9 9
10 10 #include <assert.h>
11 11 #include <errno.h>
12 12 #include <fcntl.h>
13 13 #include <signal.h>
14 14 #include <stdio.h>
15 15 #include <stdlib.h>
16 16 #include <string.h>
17 17 #include <sys/file.h>
18 18 #include <sys/stat.h>
19 19 #include <sys/types.h>
20 20 #include <sys/un.h>
21 21 #include <sys/wait.h>
22 22 #include <time.h>
23 23 #include <unistd.h>
24 24
25 25 #include "hgclient.h"
26 26 #include "util.h"
27 27
28 28 #ifndef UNIX_PATH_MAX
29 29 #define UNIX_PATH_MAX (sizeof(((struct sockaddr_un *)NULL)->sun_path))
30 30 #endif
31 31
32 32 struct cmdserveropts {
33 33 char sockname[UNIX_PATH_MAX];
34 34 char redirectsockname[UNIX_PATH_MAX];
35 35 char lockfile[UNIX_PATH_MAX];
36 36 size_t argsize;
37 37 const char **args;
38 38 int lockfd;
39 39 };
40 40
41 41 static void initcmdserveropts(struct cmdserveropts *opts) {
42 42 memset(opts, 0, sizeof(struct cmdserveropts));
43 43 opts->lockfd = -1;
44 44 }
45 45
46 46 static void freecmdserveropts(struct cmdserveropts *opts) {
47 47 free(opts->args);
48 48 opts->args = NULL;
49 49 opts->argsize = 0;
50 50 }
51 51
52 52 /*
53 53 * Test if an argument is a sensitive flag that should be passed to the server.
54 54 * Return 0 if not, otherwise the number of arguments starting from the current
55 55 * one that should be passed to the server.
56 56 */
57 57 static size_t testsensitiveflag(const char *arg)
58 58 {
59 59 static const struct {
60 60 const char *name;
61 61 size_t narg;
62 62 } flags[] = {
63 63 {"--config", 1},
64 64 {"--cwd", 1},
65 65 {"--repo", 1},
66 66 {"--repository", 1},
67 67 {"--traceback", 0},
68 68 {"-R", 1},
69 69 };
70 70 size_t i;
71 71 for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) {
72 72 size_t len = strlen(flags[i].name);
73 73 size_t narg = flags[i].narg;
74 74 if (memcmp(arg, flags[i].name, len) == 0) {
75 75 if (arg[len] == '\0') { /* --flag (value) */
76 76 return narg + 1;
77 77 } else if (arg[len] == '=' && narg > 0) { /* --flag=value */
78 78 return 1;
79 79 } else if (flags[i].name[1] != '-') { /* short flag */
80 80 return 1;
81 81 }
82 82 }
83 83 }
84 84 return 0;
85 85 }
86 86
87 87 /*
88 88 * Parse argv[] and put sensitive flags to opts->args
89 89 */
90 90 static void setcmdserverargs(struct cmdserveropts *opts,
91 91 int argc, const char *argv[])
92 92 {
93 93 size_t i, step;
94 94 opts->argsize = 0;
95 95 for (i = 0, step = 1; i < (size_t)argc; i += step, step = 1) {
96 96 if (!argv[i])
97 97 continue; /* pass clang-analyse */
98 98 if (strcmp(argv[i], "--") == 0)
99 99 break;
100 100 size_t n = testsensitiveflag(argv[i]);
101 101 if (n == 0 || i + n > (size_t)argc)
102 102 continue;
103 103 opts->args = reallocx(opts->args,
104 104 (n + opts->argsize) * sizeof(char *));
105 105 memcpy(opts->args + opts->argsize, argv + i,
106 106 sizeof(char *) * n);
107 107 opts->argsize += n;
108 108 step = n;
109 109 }
110 110 }
111 111
112 112 static void preparesockdir(const char *sockdir)
113 113 {
114 114 int r;
115 115 r = mkdir(sockdir, 0700);
116 116 if (r < 0 && errno != EEXIST)
117 117 abortmsg("cannot create sockdir %s (errno = %d)",
118 118 sockdir, errno);
119 119
120 120 struct stat st;
121 121 r = lstat(sockdir, &st);
122 122 if (r < 0)
123 123 abortmsg("cannot stat %s (errno = %d)", sockdir, errno);
124 124 if (!S_ISDIR(st.st_mode))
125 125 abortmsg("cannot create sockdir %s (file exists)", sockdir);
126 126 if (st.st_uid != geteuid() || st.st_mode & 0077)
127 127 abortmsg("insecure sockdir %s", sockdir);
128 128 }
129 129
130 130 static void setcmdserveropts(struct cmdserveropts *opts)
131 131 {
132 132 int r;
133 133 char sockdir[UNIX_PATH_MAX];
134 134 const char *envsockname = getenv("CHGSOCKNAME");
135 135 if (!envsockname) {
136 136 /* by default, put socket file in secure directory
137 137 * (permission of socket file may be ignored on some Unices) */
138 138 const char *tmpdir = getenv("TMPDIR");
139 139 if (!tmpdir)
140 140 tmpdir = "/tmp";
141 141 r = snprintf(sockdir, sizeof(sockdir), "%s/chg%d",
142 142 tmpdir, geteuid());
143 143 if (r < 0 || (size_t)r >= sizeof(sockdir))
144 144 abortmsg("too long TMPDIR (r = %d)", r);
145 145 preparesockdir(sockdir);
146 146 }
147 147
148 148 const char *basename = (envsockname) ? envsockname : sockdir;
149 149 const char *sockfmt = (envsockname) ? "%s" : "%s/server";
150 150 const char *lockfmt = (envsockname) ? "%s.lock" : "%s/lock";
151 151 r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename);
152 152 if (r < 0 || (size_t)r >= sizeof(opts->sockname))
153 153 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
154 154 r = snprintf(opts->lockfile, sizeof(opts->lockfile), lockfmt, basename);
155 155 if (r < 0 || (size_t)r >= sizeof(opts->lockfile))
156 156 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
157 157 }
158 158
159 159 /*
160 160 * Acquire a file lock that indicates a client is trying to start and connect
161 161 * to a server, before executing a command. The lock is released upon exit or
162 162 * explicit unlock. Will block if the lock is held by another process.
163 163 */
164 164 static void lockcmdserver(struct cmdserveropts *opts)
165 165 {
166 166 if (opts->lockfd == -1) {
167 167 opts->lockfd = open(opts->lockfile, O_RDWR | O_CREAT | O_NOFOLLOW, 0600);
168 168 if (opts->lockfd == -1)
169 169 abortmsg("cannot create lock file %s", opts->lockfile);
170 170 }
171 171 int r = flock(opts->lockfd, LOCK_EX);
172 172 if (r == -1)
173 173 abortmsg("cannot acquire lock");
174 174 }
175 175
176 176 /*
177 177 * Release the file lock held by calling lockcmdserver. Will do nothing if
178 178 * lockcmdserver is not called.
179 179 */
180 180 static void unlockcmdserver(struct cmdserveropts *opts)
181 181 {
182 182 if (opts->lockfd == -1)
183 183 return;
184 184 flock(opts->lockfd, LOCK_UN);
185 185 close(opts->lockfd);
186 186 opts->lockfd = -1;
187 187 }
188 188
189 189 static const char *gethgcmd(void)
190 190 {
191 191 static const char *hgcmd = NULL;
192 192 if (!hgcmd) {
193 193 hgcmd = getenv("CHGHG");
194 194 if (!hgcmd || hgcmd[0] == '\0')
195 195 hgcmd = getenv("HG");
196 196 if (!hgcmd || hgcmd[0] == '\0')
197 197 #ifdef HGPATH
198 198 hgcmd = (HGPATH);
199 199 #else
200 200 hgcmd = "hg";
201 201 #endif
202 202 }
203 203 return hgcmd;
204 204 }
205 205
206 206 static void execcmdserver(const struct cmdserveropts *opts)
207 207 {
208 208 const char *hgcmd = gethgcmd();
209 209
210 210 const char *baseargv[] = {
211 211 hgcmd,
212 212 "serve",
213 213 "--cmdserver", "chgunix",
214 214 "--address", opts->sockname,
215 215 "--daemon-postexec", "chdir:/",
216 216 "--config", "extensions.chgserver=",
217 217 };
218 218 size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]);
219 219 size_t argsize = baseargvsize + opts->argsize + 1;
220 220
221 221 const char **argv = mallocx(sizeof(char *) * argsize);
222 222 memcpy(argv, baseargv, sizeof(baseargv));
223 223 memcpy(argv + baseargvsize, opts->args, sizeof(char *) * opts->argsize);
224 224 argv[argsize - 1] = NULL;
225 225
226 226 if (putenv("CHGINTERNALMARK=") != 0)
227 227 abortmsg("failed to putenv (errno = %d)", errno);
228 228 if (execvp(hgcmd, (char **)argv) < 0)
229 229 abortmsg("failed to exec cmdserver (errno = %d)", errno);
230 230 free(argv);
231 231 }
232 232
233 233 /* Retry until we can connect to the server. Give up after some time. */
234 234 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
235 235 {
236 236 static const struct timespec sleepreq = {0, 10 * 1000000};
237 237 int pst = 0;
238 238
239 239 debugmsg("try connect to %s repeatedly", opts->sockname);
240 240 for (unsigned int i = 0; i < 10 * 100; i++) {
241 241 hgclient_t *hgc = hgc_open(opts->sockname);
242 242 if (hgc)
243 243 return hgc;
244 244
245 245 if (pid > 0) {
246 246 /* collect zombie if child process fails to start */
247 247 int r = waitpid(pid, &pst, WNOHANG);
248 248 if (r != 0)
249 249 goto cleanup;
250 250 }
251 251
252 252 nanosleep(&sleepreq, NULL);
253 253 }
254 254
255 255 abortmsg("timed out waiting for cmdserver %s", opts->sockname);
256 256 return NULL;
257 257
258 258 cleanup:
259 259 if (WIFEXITED(pst)) {
260 260 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
261 261 exit(WEXITSTATUS(pst));
262 262 } else if (WIFSIGNALED(pst)) {
263 263 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
264 264 } else {
265 265 abortmsg("error white waiting cmdserver");
266 266 }
267 267 return NULL;
268 268 }
269 269
270 270 /* Connect to a cmdserver. Will start a new server on demand. */
271 271 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
272 272 {
273 273 const char *sockname = opts->redirectsockname[0] ?
274 274 opts->redirectsockname : opts->sockname;
275 275 debugmsg("try connect to %s", sockname);
276 276 hgclient_t *hgc = hgc_open(sockname);
277 277 if (hgc)
278 278 return hgc;
279 279
280 280 lockcmdserver(opts);
281 281 hgc = hgc_open(sockname);
282 282 if (hgc) {
283 283 unlockcmdserver(opts);
284 284 debugmsg("cmdserver is started by another process");
285 285 return hgc;
286 286 }
287 287
288 288 /* prevent us from being connected to an outdated server: we were
289 289 * told by a server to redirect to opts->redirectsockname and that
290 290 * address does not work. we do not want to connect to the server
291 291 * again because it will probably tell us the same thing. */
292 292 if (sockname == opts->redirectsockname)
293 293 unlink(opts->sockname);
294 294
295 295 debugmsg("start cmdserver at %s", opts->sockname);
296 296
297 297 pid_t pid = fork();
298 298 if (pid < 0)
299 299 abortmsg("failed to fork cmdserver process");
300 300 if (pid == 0) {
301 301 /* do not leak lockfd to hg */
302 302 close(opts->lockfd);
303 303 execcmdserver(opts);
304 304 } else {
305 305 hgc = retryconnectcmdserver(opts, pid);
306 306 }
307 307
308 308 unlockcmdserver(opts);
309 309 return hgc;
310 310 }
311 311
312 312 static void killcmdserver(const struct cmdserveropts *opts)
313 313 {
314 314 /* resolve config hash */
315 315 char *resolvedpath = realpath(opts->sockname, NULL);
316 316 if (resolvedpath) {
317 317 unlink(resolvedpath);
318 318 free(resolvedpath);
319 319 }
320 320 }
321 321
322 322 static pid_t peerpid = 0;
323 323
324 324 static void forwardsignal(int sig)
325 325 {
326 326 assert(peerpid > 0);
327 327 if (kill(peerpid, sig) < 0)
328 328 abortmsg("cannot kill %d (errno = %d)", peerpid, errno);
329 329 debugmsg("forward signal %d", sig);
330 330 }
331 331
332 332 static void handlestopsignal(int sig)
333 333 {
334 334 sigset_t unblockset, oldset;
335 335 struct sigaction sa, oldsa;
336 336 if (sigemptyset(&unblockset) < 0)
337 337 goto error;
338 338 if (sigaddset(&unblockset, sig) < 0)
339 339 goto error;
340 340 memset(&sa, 0, sizeof(sa));
341 341 sa.sa_handler = SIG_DFL;
342 342 sa.sa_flags = SA_RESTART;
343 343 if (sigemptyset(&sa.sa_mask) < 0)
344 344 goto error;
345 345
346 346 forwardsignal(sig);
347 347 if (raise(sig) < 0) /* resend to self */
348 348 goto error;
349 349 if (sigaction(sig, &sa, &oldsa) < 0)
350 350 goto error;
351 351 if (sigprocmask(SIG_UNBLOCK, &unblockset, &oldset) < 0)
352 352 goto error;
353 353 /* resent signal will be handled before sigprocmask() returns */
354 354 if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0)
355 355 goto error;
356 356 if (sigaction(sig, &oldsa, NULL) < 0)
357 357 goto error;
358 358 return;
359 359
360 360 error:
361 361 abortmsg("failed to handle stop signal (errno = %d)", errno);
362 362 }
363 363
364 364 static void setupsignalhandler(pid_t pid)
365 365 {
366 366 if (pid <= 0)
367 367 return;
368 368 peerpid = pid;
369 369
370 370 struct sigaction sa;
371 371 memset(&sa, 0, sizeof(sa));
372 372 sa.sa_handler = forwardsignal;
373 373 sa.sa_flags = SA_RESTART;
374 374 if (sigemptyset(&sa.sa_mask) < 0)
375 375 goto error;
376 376
377 377 if (sigaction(SIGHUP, &sa, NULL) < 0)
378 378 goto error;
379 379 if (sigaction(SIGINT, &sa, NULL) < 0)
380 380 goto error;
381 381
382 382 /* terminate frontend by double SIGTERM in case of server freeze */
383 383 sa.sa_flags |= SA_RESETHAND;
384 384 if (sigaction(SIGTERM, &sa, NULL) < 0)
385 385 goto error;
386 386
387 387 /* propagate job control requests to worker */
388 388 sa.sa_handler = forwardsignal;
389 389 sa.sa_flags = SA_RESTART;
390 390 if (sigaction(SIGCONT, &sa, NULL) < 0)
391 391 goto error;
392 392 sa.sa_handler = handlestopsignal;
393 393 sa.sa_flags = SA_RESTART;
394 394 if (sigaction(SIGTSTP, &sa, NULL) < 0)
395 395 goto error;
396 396
397 397 return;
398 398
399 399 error:
400 400 abortmsg("failed to set up signal handlers (errno = %d)", errno);
401 401 }
402 402
403 403 /* This implementation is based on hgext/pager.py (pre 369741ef7253) */
404 404 static void setuppager(hgclient_t *hgc, const char *const args[],
405 405 size_t argsize)
406 406 {
407 407 const char *pagercmd = hgc_getpager(hgc, args, argsize);
408 408 if (!pagercmd)
409 409 return;
410 410
411 411 int pipefds[2];
412 412 if (pipe(pipefds) < 0)
413 413 return;
414 414 pid_t pid = fork();
415 415 if (pid < 0)
416 416 goto error;
417 417 if (pid == 0) {
418 418 close(pipefds[0]);
419 419 if (dup2(pipefds[1], fileno(stdout)) < 0)
420 420 goto error;
421 421 if (isatty(fileno(stderr))) {
422 422 if (dup2(pipefds[1], fileno(stderr)) < 0)
423 423 goto error;
424 424 }
425 425 close(pipefds[1]);
426 426 hgc_attachio(hgc); /* reattach to pager */
427 427 return;
428 428 } else {
429 429 dup2(pipefds[0], fileno(stdin));
430 430 close(pipefds[0]);
431 431 close(pipefds[1]);
432 432
433 433 int r = execlp("/bin/sh", "/bin/sh", "-c", pagercmd, NULL);
434 434 if (r < 0) {
435 435 abortmsg("cannot start pager '%s' (errno = %d)",
436 436 pagercmd, errno);
437 437 }
438 438 return;
439 439 }
440 440
441 441 error:
442 442 close(pipefds[0]);
443 443 close(pipefds[1]);
444 444 abortmsg("failed to prepare pager (errno = %d)", errno);
445 445 }
446 446
447 447 /* Run instructions sent from the server like unlink and set redirect path
448 448 * Return 1 if reconnect is needed, otherwise 0 */
449 449 static int runinstructions(struct cmdserveropts *opts, const char **insts)
450 450 {
451 451 int needreconnect = 0;
452 452 if (!insts)
453 453 return needreconnect;
454 454
455 455 assert(insts);
456 456 opts->redirectsockname[0] = '\0';
457 457 const char **pinst;
458 458 for (pinst = insts; *pinst; pinst++) {
459 459 debugmsg("instruction: %s", *pinst);
460 460 if (strncmp(*pinst, "unlink ", 7) == 0) {
461 461 unlink(*pinst + 7);
462 462 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
463 463 int r = snprintf(opts->redirectsockname,
464 464 sizeof(opts->redirectsockname),
465 465 "%s", *pinst + 9);
466 466 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
467 467 abortmsg("redirect path is too long (%d)", r);
468 468 needreconnect = 1;
469 469 } else if (strncmp(*pinst, "exit ", 5) == 0) {
470 470 int n = 0;
471 471 if (sscanf(*pinst + 5, "%d", &n) != 1)
472 472 abortmsg("cannot read the exit code");
473 473 exit(n);
474 474 } else if (strcmp(*pinst, "reconnect") == 0) {
475 475 needreconnect = 1;
476 476 } else {
477 477 abortmsg("unknown instruction: %s", *pinst);
478 478 }
479 479 }
480 480 return needreconnect;
481 481 }
482 482
483 483 /*
484 484 * Test whether the command is unsupported or not. This is not designed to
485 485 * cover all cases. But it's fast, does not depend on the server and does
486 486 * not return false positives.
487 487 */
488 488 static int isunsupported(int argc, const char *argv[])
489 489 {
490 490 enum {
491 491 SERVE = 1,
492 492 DAEMON = 2,
493 493 SERVEDAEMON = SERVE | DAEMON,
494 494 TIME = 4,
495 495 };
496 496 unsigned int state = 0;
497 497 int i;
498 498 for (i = 0; i < argc; ++i) {
499 499 if (strcmp(argv[i], "--") == 0)
500 500 break;
501 501 if (i == 0 && strcmp("serve", argv[i]) == 0)
502 502 state |= SERVE;
503 503 else if (strcmp("-d", argv[i]) == 0 ||
504 504 strcmp("--daemon", argv[i]) == 0)
505 505 state |= DAEMON;
506 506 else if (strcmp("--time", argv[i]) == 0)
507 507 state |= TIME;
508 508 }
509 509 return (state & TIME) == TIME ||
510 510 (state & SERVEDAEMON) == SERVEDAEMON;
511 511 }
512 512
513 513 static void execoriginalhg(const char *argv[])
514 514 {
515 515 debugmsg("execute original hg");
516 516 if (execvp(gethgcmd(), (char **)argv) < 0)
517 517 abortmsg("failed to exec original hg (errno = %d)", errno);
518 518 }
519 519
520 520 int main(int argc, const char *argv[], const char *envp[])
521 521 {
522 522 if (getenv("CHGDEBUG"))
523 523 enabledebugmsg();
524 524
525 if (!getenv("HGPLAIN") && isatty(fileno(stderr)))
526 enablecolor();
527
525 528 if (getenv("CHGINTERNALMARK"))
526 529 abortmsg("chg started by chg detected.\n"
527 530 "Please make sure ${HG:-hg} is not a symlink or "
528 531 "wrapper to chg. Alternatively, set $CHGHG to the "
529 532 "path of real hg.");
530 533
531 534 if (isunsupported(argc - 1, argv + 1))
532 535 execoriginalhg(argv);
533 536
534 537 struct cmdserveropts opts;
535 538 initcmdserveropts(&opts);
536 539 setcmdserveropts(&opts);
537 540 setcmdserverargs(&opts, argc, argv);
538 541
539 542 if (argc == 2) {
540 543 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
541 544 killcmdserver(&opts);
542 545 return 0;
543 546 }
544 547 }
545 548
546 549 hgclient_t *hgc;
547 550 size_t retry = 0;
548 551 while (1) {
549 552 hgc = connectcmdserver(&opts);
550 553 if (!hgc)
551 554 abortmsg("cannot open hg client");
552 555 hgc_setenv(hgc, envp);
553 556 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
554 557 int needreconnect = runinstructions(&opts, insts);
555 558 free(insts);
556 559 if (!needreconnect)
557 560 break;
558 561 hgc_close(hgc);
559 562 if (++retry > 10)
560 563 abortmsg("too many redirections.\n"
561 564 "Please make sure %s is not a wrapper which "
562 565 "changes sensitive environment variables "
563 566 "before executing hg. If you have to use a "
564 567 "wrapper, wrap chg instead of hg.",
565 568 gethgcmd());
566 569 }
567 570
568 571 setupsignalhandler(hgc_peerpid(hgc));
569 572 setuppager(hgc, argv + 1, argc - 1);
570 573 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
571 574 hgc_close(hgc);
572 575 freecmdserveropts(&opts);
573 576 return exitcode;
574 577 }
@@ -1,139 +1,157
1 1 /*
2 2 * Utility functions
3 3 *
4 4 * Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org>
5 5 *
6 6 * This software may be used and distributed according to the terms of the
7 7 * GNU General Public License version 2 or any later version.
8 8 */
9 9
10 10 #include <signal.h>
11 11 #include <stdarg.h>
12 12 #include <stdio.h>
13 13 #include <stdlib.h>
14 14 #include <string.h>
15 15 #include <sys/types.h>
16 16 #include <sys/wait.h>
17 17 #include <unistd.h>
18 18
19 19 #include "util.h"
20 20
21 static int colorenabled = 0;
22
23 static inline void fsetcolor(FILE *fp, const char *code)
24 {
25 if (!colorenabled)
26 return;
27 fprintf(fp, "\033[%sm", code);
28 }
29
21 30 void abortmsg(const char *fmt, ...)
22 31 {
23 32 va_list args;
24 33 va_start(args, fmt);
25 fputs("\033[1;31mchg: abort: ", stderr);
34 fsetcolor(stderr, "1;31");
35 fputs("chg: abort: ", stderr);
26 36 vfprintf(stderr, fmt, args);
27 fputs("\033[m\n", stderr);
37 fsetcolor(stderr, "");
38 fputc('\n', stderr);
28 39 va_end(args);
29 40
30 41 exit(255);
31 42 }
32 43
33 44 static int debugmsgenabled = 0;
34 45
46 void enablecolor(void)
47 {
48 colorenabled = 1;
49 }
50
35 51 void enabledebugmsg(void)
36 52 {
37 53 debugmsgenabled = 1;
38 54 }
39 55
40 56 void debugmsg(const char *fmt, ...)
41 57 {
42 58 if (!debugmsgenabled)
43 59 return;
44 60
45 61 va_list args;
46 62 va_start(args, fmt);
47 fputs("\033[1;30mchg: debug: ", stderr);
63 fsetcolor(stderr, "1;30");
64 fputs("chg: debug: ", stderr);
48 65 vfprintf(stderr, fmt, args);
49 fputs("\033[m\n", stderr);
66 fsetcolor(stderr, "");
67 fputc('\n', stderr);
50 68 va_end(args);
51 69 }
52 70
53 71 void *mallocx(size_t size)
54 72 {
55 73 void *result = malloc(size);
56 74 if (!result)
57 75 abortmsg("failed to malloc");
58 76 return result;
59 77 }
60 78
61 79 void *reallocx(void *ptr, size_t size)
62 80 {
63 81 void *result = realloc(ptr, size);
64 82 if (!result)
65 83 abortmsg("failed to realloc");
66 84 return result;
67 85 }
68 86
69 87 /*
70 88 * Execute a shell command in mostly the same manner as system(), with the
71 89 * give environment variables, after chdir to the given cwd. Returns a status
72 90 * code compatible with the Python subprocess module.
73 91 */
74 92 int runshellcmd(const char *cmd, const char *envp[], const char *cwd)
75 93 {
76 94 enum { F_SIGINT = 1, F_SIGQUIT = 2, F_SIGMASK = 4, F_WAITPID = 8 };
77 95 unsigned int doneflags = 0;
78 96 int status = 0;
79 97 struct sigaction newsa, oldsaint, oldsaquit;
80 98 sigset_t oldmask;
81 99
82 100 /* block or mask signals just as system() does */
83 101 memset(&newsa, 0, sizeof(newsa));
84 102 newsa.sa_handler = SIG_IGN;
85 103 newsa.sa_flags = 0;
86 104 if (sigemptyset(&newsa.sa_mask) < 0)
87 105 goto done;
88 106 if (sigaction(SIGINT, &newsa, &oldsaint) < 0)
89 107 goto done;
90 108 doneflags |= F_SIGINT;
91 109 if (sigaction(SIGQUIT, &newsa, &oldsaquit) < 0)
92 110 goto done;
93 111 doneflags |= F_SIGQUIT;
94 112
95 113 if (sigaddset(&newsa.sa_mask, SIGCHLD) < 0)
96 114 goto done;
97 115 if (sigprocmask(SIG_BLOCK, &newsa.sa_mask, &oldmask) < 0)
98 116 goto done;
99 117 doneflags |= F_SIGMASK;
100 118
101 119 pid_t pid = fork();
102 120 if (pid < 0)
103 121 goto done;
104 122 if (pid == 0) {
105 123 sigaction(SIGINT, &oldsaint, NULL);
106 124 sigaction(SIGQUIT, &oldsaquit, NULL);
107 125 sigprocmask(SIG_SETMASK, &oldmask, NULL);
108 126 if (cwd && chdir(cwd) < 0)
109 127 _exit(127);
110 128 const char *argv[] = {"sh", "-c", cmd, NULL};
111 129 if (envp) {
112 130 execve("/bin/sh", (char **)argv, (char **)envp);
113 131 } else {
114 132 execv("/bin/sh", (char **)argv);
115 133 }
116 134 _exit(127);
117 135 } else {
118 136 if (waitpid(pid, &status, 0) < 0)
119 137 goto done;
120 138 doneflags |= F_WAITPID;
121 139 }
122 140
123 141 done:
124 142 if (doneflags & F_SIGINT)
125 143 sigaction(SIGINT, &oldsaint, NULL);
126 144 if (doneflags & F_SIGQUIT)
127 145 sigaction(SIGQUIT, &oldsaquit, NULL);
128 146 if (doneflags & F_SIGMASK)
129 147 sigprocmask(SIG_SETMASK, &oldmask, NULL);
130 148
131 149 /* no way to report other errors, use 127 (= shell termination) */
132 150 if (!(doneflags & F_WAITPID))
133 151 return 127;
134 152 if (WIFEXITED(status))
135 153 return WEXITSTATUS(status);
136 154 if (WIFSIGNALED(status))
137 155 return -WTERMSIG(status);
138 156 return 127;
139 157 }
@@ -1,29 +1,30
1 1 /*
2 2 * Utility functions
3 3 *
4 4 * Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org>
5 5 *
6 6 * This software may be used and distributed according to the terms of the
7 7 * GNU General Public License version 2 or any later version.
8 8 */
9 9
10 10 #ifndef UTIL_H_
11 11 #define UTIL_H_
12 12
13 13 #ifdef __GNUC__
14 14 #define PRINTF_FORMAT_ __attribute__((format(printf, 1, 2)))
15 15 #else
16 16 #define PRINTF_FORMAT_
17 17 #endif
18 18
19 19 void abortmsg(const char *fmt, ...) PRINTF_FORMAT_;
20 20
21 void enablecolor(void);
21 22 void enabledebugmsg(void);
22 23 void debugmsg(const char *fmt, ...) PRINTF_FORMAT_;
23 24
24 25 void *mallocx(size_t size);
25 26 void *reallocx(void *ptr, size_t size);
26 27
27 28 int runshellcmd(const char *cmd, const char *envp[], const char *cwd);
28 29
29 30 #endif /* UTIL_H_ */
General Comments 0
You need to be logged in to leave comments. Login now