/* * Copyright (C) 2011 Nick Bowler * * The Mutt email client opens mail attachments with external programs * synchronously: the attachment is decoded and saved to /tmp, the filename * is passed to the program to be run, and when the program exits, the file * is unlinked. This leads to an obvious problem: if the attachment-viewing * program runs in the foreground, one cannot interact with mutt until you * quit it. If the program runs in the background, then there is a race * between the program opening the file and mutt unlinking it. * * This tool solves this problem by opening all files and spawning a program * in the background. It is safe to unlink the files after they are opened, * so the race with mutt is avoided. * * License WTFPL2: Do What The Fuck You Want To Public License, version 2. * This is free software: you are free to do what the fuck you want to. * There is NO WARRANTY, to the extent permitted by law. */ #define _GNU_SOURCE #include #include #include #include #include #include #include static const char *progname = "openexec"; static const char sopts[] = "-VH"; static const struct option lopts[] = { { "version", 0, NULL, 'V' }, { "help", 0, NULL, 'H' }, { 0 } }; static void print_version(void) { puts("openexec version 1.0"); puts("\ Copyright (C) 2011 Nick Bowler.\n\ License WTFPL2: Do What The Fuck You Want To Public License, version 2.\n\ This is free software: you are free to do what the fuck you want to.\n\ There is NO WARRANTY, to the extent permitted by law.\ "); } static void print_usage(FILE *f) { fprintf(f, "Usage: %s [options] [file ...] -- program [arguments]\n", progname); } static void print_help(void) { print_usage(stdout); puts("Detailed help coming eventually."); } static char *fake_open(const char *filename) { char *buf; int n, fd; fd = open(filename, O_RDONLY); if (fd == -1) { fprintf(stderr, "%s: %s: %s\n", progname, filename, strerror(errno)); return NULL; } n = snprintf(NULL, 0, "/proc/self/fd/%d", fd); buf = malloc(n+1); if (!buf) { close(fd); fprintf(stderr, "%s: %s\n", progname, strerror(errno)); return NULL; } sprintf(buf, "/proc/self/fd/%d", fd); return buf; } int main(int argc, char **argv) { int nargs = 0, opt, rc, err, fds[2]; if (argc > 0) progname = argv[0]; char *args[argc]; while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) { switch (opt) { case 1: args[nargs] = fake_open(optarg); if (!args[nargs]) return EXIT_FAILURE; nargs++; break; case 'V': print_version(); return EXIT_SUCCESS; case 'H': print_help(); return EXIT_SUCCESS; default: print_usage(stderr); return EXIT_FAILURE; } } if (!argv[optind]) { print_usage(stderr); return EXIT_FAILURE; } args[nargs] = NULL; memmove(args + (argc - optind), args, (nargs + 1) * sizeof *args); memcpy(args, argv + optind, (argc - optind) * sizeof *args); /* * We use a close-on-exec pipe for the child to signal success/failure * to the parent. On successful exec, the write end of the pipe will * be closed, causing the read in the parent to return 0. On failure, * errno will be written to the pipe, allowing the parent to print * an error message. */ rc = pipe2(fds, O_CLOEXEC); if (rc == -1) { fprintf(stderr, "%s: pipe2: %s\n", progname, strerror(errno)); return EXIT_FAILURE; } switch (fork()) { case -1: fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); return EXIT_FAILURE; case 0: execvp(args[0], args); /* This horrible if statement shuts up GCC... */ err = errno; if (write(fds[1], &err, sizeof err)) ; _Exit(EXIT_FAILURE); } close(fds[1]); rc = read(fds[0], &err, sizeof err); if (rc == -1) { /* No information about the child's state. */ fprintf(stderr, "%s: read: %s\n", progname, strerror(errno)); return EXIT_FAILURE; } else if (rc == 0) { /* Exec was successful, so we are too. */ return EXIT_SUCCESS; } /* Exec in the child failed, print the error we received on the pipe. */ fprintf(stderr, "%s: %s: %s\n", progname, args[0], strerror(err)); return EXIT_FAILURE; }