/* out.c */ #include #include /* libfetish error() */ #include #include #include #include #include #include #include #include /* exit codes indicate which filestream caused an error (input, * output, or the temporary file) */ enum { BADARGS=1, BADTMP, BADIN, BADOUT }; /* these 3 functions exit the program on failure; return always * indicates success */ void mktmp(void); void wrtmp(const char *, ssize_t); void mvtmp(const char *); static char *fname; /* output file name */ static int append; /* boolean */ int main(int argc, char **argv) { int c; ssize_t n; char buf[BUFSIZ]; while ((c = getopt(argc, argv, "a")) != -1) switch (c) { case 'a': append=1; break; } if (argc - optind != 1) /* require exactly one file argument */ error(BADARGS, 0, "Usage: out [-a] "); fname = argv[optind]; mktmp(); while ((n = read(STDIN_FILENO, buf, BUFSIZ)) > 0) wrtmp(buf, n); if (n < 0) error(BADIN, errno, "couldn't read from stdin"); mvtmp(fname); exit(EXIT_SUCCESS); } static int tmp_fd = -1; static char *tmp_name = 0; void mktmp(void) { FILE *f; int tmpfd(void); if ((tmp_fd = tmpfd()) >= 0) return; if ((f = tmpfile()) == NULL) error(BADTMP, errno, "couldn't create temporary file"); tmp_fd = fileno(f); } void wrtmp(const char *buf, ssize_t n) { if (write(tmp_fd, buf, n) != n) error(BADTMP, errno, "couldn't write to temporary file"); } void mvtmp(const char *dest) { char buf[BUFSIZ]; ssize_t n; int fd; if (tmp_name) { if (rename(tmp_name, dest) >= 0) return; else if (unlink(tmp_name) < 0) error(0, errno, "couldn't unlink temporary file `%s'", tmp_name); } if (lseek(tmp_fd, SEEK_SET, 0) < 0) error(BADTMP, errno, "couldn't rewind temporary file (bizarre)"); if ((fd = open(dest, O_WRONLY|O_CREAT|(append ? O_APPEND : O_TRUNC), 0600)) < 0) error(BADOUT, errno, "couldn't open `%s'", dest); while ((n = read(tmp_fd, buf, BUFSIZ)) > 0) if (write(fd, buf, n) != n) error(BADOUT, errno, "couldn't write to `%s'", dest); if (n < 0) error(BADTMP, errno, "couldn't read from temporary file (bizarre)"); if (close(fd) < 0) error(BADOUT, errno, "couldn't close `%s'", dest); } int tmpfd(void) /* If we make the temporary file on the same filesystem as the * destination file we can halve IO requirements by rename()ing the * temporary instead of copying it. We copy anyway if nlink > 1, to * keep hard-links intact, or if the owner and group of the file are * not our euid and egid, to keep ownership intact. We also copy if * the file is a symlink, because it's more work to determine the * proper directory in that case. There's no need to check for write * perms on the directory, because if we don't have it the open() will * fail anyway. */ { const int RANDCHARS = 6; const int MAXTRIES = 30; int mode; struct stat st; char *p; int try; mode = 0666; if (lstat(fname, &st) < 0) { if (errno != ENOENT) return -1; } else { if (st.st_nlink > 1 || S_ISLNK(mode = st.st_mode)) return -1; if (st.st_uid != geteuid() && seteuid(st.st_uid) < 0) return -1; if (st.st_gid != getegid() && setegid(st.st_gid) < 0) return -1; } mode &= 07777; /* I'm not actually sure whether this * is necessary */ if ((tmp_name = malloc(strlen(fname) + RANDCHARS + 1)) == NULL) return -1; strcpy(tmp_name, fname); p=tmp_name + strlen(tmp_name); while (p != tmp_name && *p != '/') --p; for (try=0; try < MAXTRIES; ++try) { int i; char randalnum(void); for (i=0; i= 0) return tmp_fd; if (errno != EEXIST) return -1; } return -1; } char randalnum(void) { static char alnums[] = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789"; static short seed = 1; if (seed) seed = 0, srand(time(NULL)); return alnums[ rand() % (sizeof(alnums) - 1) ]; } char *mreadlink(const char *path) { /* like readlink, except maintain a static buffer */ static char *res = 0; static size_t sz = 128; int tmp; char *p; if (res == NULL) if ((res = malloc(sz)) == NULL) return NULL; while ((tmp = readlink(path, res, sz)) >= sz) if ((p = realloc(sz * 2)) == NULL) return NULL; else sz *= 2, res = tmp; if (tmp < 0) return NULL; }