int try_xopen(const char* pathname, int flags, mode_t mode) { struct cleanup* cl = cleanup_allocate(); int fd = open(pathname, flags | O_CLOEXEC, mode); if (fd == -1) { cleanup_forget(cl); return -1; } assert_cloexec(fd); cleanup_commit_close_fd(cl, fd); return fd; }
static void do_xfer_recv(const struct xfer_opts xfer_opts, const char* filename, const char* desired_basename, int from_peer) { struct xfer_msg statm = recv_xfer_msg(from_peer); if (statm.type != XFER_MSG_STAT) die(ECOMM, "expected stat msg"); struct cleanup* error_cl = cleanup_allocate(); struct stat st; const char* parent_directory = NULL; const char* rename_to = NULL; const char* write_mode = NULL; int dest_fd; if (stat(filename, &st) == 0) { if (S_ISDIR(st.st_mode)) { if (desired_basename == NULL) die(EISDIR, "\"%s\" is a directory", filename); parent_directory = filename; filename = xaprintf("%s/%s", parent_directory, desired_basename); } else if (S_ISREG(st.st_mode)) { if (st.st_nlink > 1) write_mode = "inplace"; } else { write_mode = "inplace"; } } if (parent_directory == NULL) parent_directory = xdirname(filename); if (write_mode == NULL) write_mode = xfer_opts.write_mode; bool atomic; bool automatic_mode = false; if (write_mode == NULL) { automatic_mode = true; atomic = true; } else if (strcmp(write_mode, "atomic") == 0) { atomic = true; } else if (strcmp(write_mode, "inplace") == 0) { atomic = false; } else { die(EINVAL, "unknown write mode \"%s\"", write_mode); } bool regular_file = true; bool preallocated = false; bool chmod_explicit = false; mode_t chmod_explicit_modes = 0; if (xfer_opts.preserve) { chmod_explicit = true; chmod_explicit_modes = statm.u.stat.ugo_bits; } if (xfer_opts.mode) { char* endptr = NULL; errno = 0; unsigned long omode = strtoul(xfer_opts.mode, &endptr, 8); if (errno != 0 || *endptr != '\0' || (omode &~ 0777) != 0) die(EINVAL, "invalid mode bits: %s", xfer_opts.mode); chmod_explicit = true; chmod_explicit_modes = (mode_t) omode; } mode_t creat_mode = (chmod_explicit_modes ? 0200 : 0666); if (atomic) { rename_to = filename; filename = xaprintf("%s.fb-adb-%s", filename, gen_hex_random(ENOUGH_ENTROPY)); dest_fd = try_xopen( filename, O_CREAT | O_WRONLY | O_EXCL, creat_mode); if (dest_fd == -1) { if (errno == EACCES && automatic_mode) { atomic = false; filename = rename_to; rename_to = NULL; } else { die_errno("open(\"%s\")", filename); } } } if (!atomic) { dest_fd = xopen(filename, O_WRONLY | O_CREAT | O_TRUNC, creat_mode); if (!S_ISREG(xfstat(dest_fd).st_mode)) regular_file = false; } if (regular_file) cleanup_commit(error_cl, unlink_cleanup, filename); if (regular_file && statm.u.stat.size > 0) preallocated = fallocate_if_supported( dest_fd, statm.u.stat.size); uint64_t total_written = copy_loop_posix_recv(from_peer, dest_fd); if (preallocated && total_written < statm.u.stat.size) xftruncate(dest_fd, total_written); if (xfer_opts.preserve) { struct timeval times[2] = { { statm.u.stat.atime, statm.u.stat.atime_ns / 1000 }, { statm.u.stat.mtime, statm.u.stat.mtime_ns / 1000 }, }; #ifdef HAVE_FUTIMES if (futimes(dest_fd, times) == -1) die_errno("futimes"); #else if (utimes(filename, times) == -1) die_errno("times"); #endif } if (chmod_explicit) if (fchmod(dest_fd, chmod_explicit_modes) == -1) die_errno("fchmod"); if (xfer_opts.sync) xfsync(dest_fd); if (rename_to) xrename(filename, rename_to); if (xfer_opts.sync) xfsync(xopen(parent_directory, O_DIRECTORY|O_RDONLY, 0)); cleanup_forget(error_cl); }