/******************************************************************************* * * timepreserve.c * * AUTHOR: * Dan Harkless * * COPYRIGHT: * This file is Copyright (C) 2002 by Dan Harkless, and is released under the * GNU General Public License . * * USAGE: * % timepreserve [...] * * EXAMPLES: * % timepreserve "touch *" * # a no-op * % timepreserve "exifedit -b -t r *.jpg" *.jpg * % timepreserve "wrjpgcom -comment 'a cow' cow.jpg > X; mv X cow.jpg" cow.jpg * * DESCRIPTION: * Preserves the access and modification timestamps of one or more files across * the execution of a commandline that would otherwise modify them. * must be passed as a single parameter (so quote it), * and is given to /bin/sh for interpretation so you can do things like I/O * redirection, as illustrated above. The exit status returned by the wrapped * commandline will be returned by timepreserve, unless one of our system calls * fail, in which case we return EXIT_FAILURE (1). * * If we fail to read the timestamps from any of the specified files, we'll * abort before running the commandline, but if writing a timestamp back to one * of those files after running the commandline fails, we'll output a * descriptive error including the timestamp we were trying to write back (so * that info doesn't get lost) and continue with the rest of the files. * * DATE MODIFICATION * ========== ================================================================== * 2002-11-28 D'oh! Subtracted 2 from argc when allocating timestamp_array but * forgot to subtract 2 when indexing into it. Program worked, but * on Linux would seg fault in libc function chunk_free() after * exiting (this didn't show up on Solaris). * 2002-03-17 Upgraded to work with more than one file. * 2002-03-16 Original. * *******************************************************************************/ #include /* supposed to precede and */ #include /* for errno */ #include /* for fprintf(), etc. */ #include /* for EXIT_FAILURE and EXIT_SUCCESS */ #include /* for strerror() and strrchr() */ #include /* for stat() and struct stat */ #include /* for waitpid(), etc. */ #include /* for execl(), etc. */ #include /* for struct utimbuf and utime() */ int main(int argc, char** argv) { char* last_slash = strrchr(argv[0], '/'); char* our_program_name; int child_pid, exit_status = EXIT_SUCCESS, i, packed_exit_status; struct stat file_stat; struct utimbuf* timestamp_array; if (last_slash == NULL) our_program_name = argv[0]; else our_program_name = last_slash + 1; if (argc < 3) { fprintf(stderr, "Usage: %s [...]\n", our_program_name); return EXIT_FAILURE; } timestamp_array = malloc((argc - 2) * sizeof(struct utimbuf)); for (i = 2; i < argc; i++) { if (stat(argv[i], &file_stat) < 0) { fprintf(stderr, "%s: \"%s\": %s. Aborting.\n", our_program_name, argv[i], strerror(errno)); return EXIT_FAILURE; } timestamp_array[i - 2].actime = file_stat.st_atime; timestamp_array[i - 2].modtime = file_stat.st_mtime; } child_pid = fork(); if (child_pid < 0) { /* fork() failed. */ fprintf(stderr, "%s: fork(): %s.\n", our_program_name, strerror(errno)); return EXIT_FAILURE; } else if (child_pid == 0) { /* We are the child. */ execl("/bin/sh", "/bin/sh", "-c", argv[1], NULL); /* If we get here, execl() failed. */ fprintf(stderr, "%s: execl(): %s.\n", our_program_name, strerror(errno)); return EXIT_FAILURE; } else /* We are the parent. Reap the child and decode exit status. */ if (waitpid(child_pid, &packed_exit_status, 0) < 0) { fprintf(stderr, "%s: waitpid(): %s.\n", our_program_name, strerror(errno)); exit_status = EXIT_FAILURE; } else { if (WIFEXITED(packed_exit_status)) exit_status = WEXITSTATUS(packed_exit_status); else if (WIFSIGNALED(packed_exit_status)) exit_status = WTERMSIG(packed_exit_status); else exit_status = EXIT_FAILURE; } for (i = 2; i < argc; i++) if (utime(argv[i], ×tamp_array[i - 2]) < 0) { fprintf(stderr, "%s: Failed to set \"%s\" to ac=%lu, mod=%lu: %s.\n", our_program_name, argv[i], timestamp_array[i - 2].actime, timestamp_array[i - 2].modtime, strerror(errno)); exit_status = EXIT_FAILURE; } return exit_status; }