/* TODO isolate important code and move to files_lib.c. */ static bool FileIsSparse(const char *filename) { MAYBE_SYNC_NOW; struct stat statbuf; int ret = stat(filename, &statbuf); assert_int_not_equal(ret, -1); Log(LOG_LEVEL_DEBUG, " st_size=%ju ST_NBYTES=%ju ST_NBLOCKS=%ju ST_BLKSIZE=%ju DEV_BSIZE=%ju", (uint64_t) statbuf.st_size, (uint64_t) ST_NBYTES(statbuf), (uint64_t) ST_NBLOCKS(statbuf), (uint64_t) ST_BLKSIZE(statbuf), (uint64_t) DEV_BSIZE); if (statbuf.st_size <= ST_NBYTES(statbuf)) { Log(LOG_LEVEL_DEBUG, "File is probably non-sparse"); return false; } else { /* We definitely know the file is sparse, since the allocated bytes * are less than the real size. */ Log(LOG_LEVEL_DEBUG, "File is definitely sparse"); return true; } }
bool CopyRegularFileDisk(const char *source, const char *destination) { bool ok1 = false, ok2 = false; /* initialize before the goto end; */ int sd = safe_open(source, O_RDONLY | O_BINARY); if (sd == -1) { Log(LOG_LEVEL_INFO, "Can't copy '%s' (open: %s)", source, GetErrorStr()); goto end; } /* We need to stat the file to get the right source permissions. */ struct stat statbuf; if (stat(source, &statbuf) == -1) { Log(LOG_LEVEL_INFO, "Can't copy '%s' (stat: %s)", source, GetErrorStr()); goto end; } /* unlink() + safe_open(O_CREAT|O_EXCL) to avoid symlink attacks and races. */ unlink(destination); int dd = safe_open(destination, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_BINARY, statbuf.st_mode); if (dd == -1) { Log(LOG_LEVEL_INFO, "Unable to open destination file while copying '%s' to '%s'" " (open: %s)", source, destination, GetErrorStr()); goto end; } size_t total_bytes_written; bool last_write_was_hole; ok1 = FileSparseCopy(sd, source, dd, destination, ST_BLKSIZE(statbuf), &total_bytes_written, &last_write_was_hole); bool do_sync = false; ok2= FileSparseClose(dd, destination, do_sync, total_bytes_written, last_write_was_hole); if (!ok1 || !ok2) { unlink(destination); } end: if (sd != -1) { close(sd); } return ok1 && ok2; }
bool CopyRegularFileDisk(const char *source, const char *destination) { int sd, dd; if ((sd = open(source, O_RDONLY | O_BINARY)) == -1) { CfOut(OUTPUT_LEVEL_INFORM, "open", "Can't copy %s!\n", source); unlink(destination); return false; } /* * We need to stat the file in order to get the right source permissions. */ struct stat statbuf; if (cfstat(source, &statbuf) == -1) { CfOut(OUTPUT_LEVEL_INFORM, "stat", "Can't copy %s!\n", source); unlink(destination); return false; } unlink(destination); /* To avoid link attacks */ if ((dd = open(destination, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_BINARY, statbuf.st_mode)) == -1) { CfOut(OUTPUT_LEVEL_INFORM, "open", "Unable to open destination file while doing %s to %s", source, destination); close(sd); unlink(destination); return false; } int buf_size = ST_BLKSIZE(dstat); char *buf = xmalloc(buf_size); bool result = CopyData(source, sd, destination, dd, buf, buf_size); if (!result) { unlink(destination); } close(sd); close(dd); free(buf); return result; }
static void init(void) { LogSetGlobalLevel(LOG_LEVEL_DEBUG); char *ok = mkdtemp(TEST_DIR); assert_int_not_equal(ok, NULL); /* Set blk_size */ struct stat statbuf; int ret1 = stat(TEST_DIR, &statbuf); assert_int_not_equal(ret1, -1); blk_size = ST_BLKSIZE(statbuf); Log(LOG_LEVEL_NOTICE, "Running sparse file tests with blocksize=%d TESTFILE_SIZE=%d", blk_size, TESTFILE_SIZE); Log(LOG_LEVEL_NOTICE, "Temporary directory: %s", TEST_DIR); // /tmp/files_copy_test-XXXXXX/subdir xsnprintf(TEST_SUBDIR, sizeof(TEST_SUBDIR), "%s/%s", TEST_DIR, "subdir"); // /tmp/files_copy_test-XXXXXX/testfile xsnprintf(TEST_SRC_FILE, sizeof(TEST_SRC_FILE), "%s/%s", TEST_DIR, TEST_FILENAME); // /tmp/files_copy_test-XXXXXX/subdir/testfile xsnprintf(TEST_DST_FILE, sizeof(TEST_DST_FILE), "%s/%s", TEST_SUBDIR, TEST_FILENAME); int ret2 = mkdir(TEST_SUBDIR, 0700); assert_int_equal(ret2, 0); SPARSE_SUPPORT_OK = true; if (!FsSupportsSparseFiles(TEST_DST_FILE)) { Log(LOG_LEVEL_NOTICE, "filesystem for directory '%s' doesn't seem to support sparse files!" " TEST WILL ONLY VERIFY FILE INTEGRITY!", TEST_DIR); SPARSE_SUPPORT_OK = false; } test_has_run[0] = true; success[0] = true; }
bool CopyRegularFileDisk(char *source, char *destination, bool make_holes) { int sd, dd, buf_size; char *buf, *cp; int n_read, *intp; long n_read_total = 0; int last_write_made_hole = 0; if ((sd = open(source, O_RDONLY | O_BINARY)) == -1) { CfOut(cf_inform, "open", "Can't copy %s!\n", source); unlink(destination); return false; } unlink(destination); /* To avoid link attacks */ if ((dd = open(destination, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_BINARY, 0600)) == -1) { close(sd); unlink(destination); return false; } buf_size = ST_BLKSIZE(dstat); buf = xmalloc(buf_size + sizeof(int)); while (true) { if ((n_read = read(sd, buf, buf_size)) == -1) { if (errno == EINTR) { continue; } close(sd); close(dd); free(buf); return false; } if (n_read == 0) { break; } n_read_total += n_read; intp = 0; if (make_holes) { buf[n_read] = 1; /* Sentinel to stop loop. */ /* Find first non-zero *word*, or the word with the sentinel. */ intp = (int *) buf; while (*intp++ == 0) { } /* Find the first non-zero *byte*, or the sentinel. */ cp = (char *) (intp - 1); while (*cp++ == 0) { } /* If we found the sentinel, the whole input block was zero, and we can make a hole. */ if (cp > buf + n_read) { /* Make a hole. */ if (lseek(dd, (off_t) n_read, SEEK_CUR) < 0L) { CfOut(cf_error, "lseek", "Copy failed (no space?) while doing %s to %s\n", source, destination); free(buf); unlink(destination); close(dd); close(sd); return false; } last_write_made_hole = 1; } else { /* Clear to indicate that a normal write is needed. */ intp = 0; } } if (intp == 0) { if (FullWrite(dd, buf, n_read) < 0) { CfOut(cf_error, "", "Copy failed (no space?) while doing %s to %s\n", source, destination); close(sd); close(dd); free(buf); unlink(destination); return false; } last_write_made_hole = 0; } } /* If the file ends with a `hole', something needs to be written at the end. Otherwise the kernel would truncate the file at the end of the last write operation. */ if (last_write_made_hole) { /* Write a null character and truncate it again. */ if (FullWrite(dd, "", 1) < 0 || ftruncate(dd, n_read_total) < 0) { CfOut(cf_error, "write", "cfengine: full_write or ftruncate error in CopyReg\n"); free(buf); unlink(destination); close(sd); close(dd); return false; } } close(sd); close(dd); free(buf); return true; }
static int copy_reg (const char *src_path, const char *dst_path, const struct cp_options *x, mode_t dst_mode, int *new_dst, struct stat const *src_sb) { char *buf; int buf_size; int dest_desc; int source_desc; struct stat sb; struct stat src_open_sb; char *cp; int *ip; int return_val = 0; off_t n_read_total = 0; int last_write_made_hole = 0; int make_holes = (x->sparse_mode == SPARSE_ALWAYS); source_desc = open (src_path, O_RDONLY); if (source_desc < 0) { error (0, errno, _("cannot open %s for reading"), quote (src_path)); return -1; } if (fstat (source_desc, &src_open_sb)) { error (0, errno, _("cannot fstat %s"), quote (src_path)); return_val = -1; goto close_src_desc; } /* Compare the source dev/ino from the open file to the incoming, saved ones obtained via a previous call to stat. */ if (! SAME_INODE (*src_sb, src_open_sb)) { error (0, 0, _("skipping file %s, as it was replaced while being copied"), quote (src_path)); return_val = -1; goto close_src_desc; } /* These semantics are required for cp. The if-block will be taken in move_mode. */ if (*new_dst) { dest_desc = open (dst_path, O_WRONLY | O_CREAT, dst_mode); } else { dest_desc = open (dst_path, O_WRONLY | O_TRUNC, dst_mode); if (dest_desc < 0 && x->unlink_dest_after_failed_open) { if (unlink (dst_path)) { error (0, errno, _("cannot remove %s"), quote (dst_path)); return_val = -1; goto close_src_desc; } /* Tell caller that the destination file was unlinked. */ *new_dst = 1; /* Try the open again, but this time with different flags. */ dest_desc = open (dst_path, O_WRONLY | O_CREAT, dst_mode); } } if (dest_desc < 0) { error (0, errno, _("cannot create regular file %s"), quote (dst_path)); return_val = -1; goto close_src_desc; } /* Determine the optimal buffer size. */ if (fstat (dest_desc, &sb)) { error (0, errno, _("cannot fstat %s"), quote (dst_path)); return_val = -1; goto close_src_and_dst_desc; } buf_size = ST_BLKSIZE (sb); #if HAVE_STRUCT_STAT_ST_BLOCKS if (x->sparse_mode == SPARSE_AUTO && S_ISREG (sb.st_mode)) { /* Use a heuristic to determine whether SRC_PATH contains any sparse blocks. */ if (fstat (source_desc, &sb)) { error (0, errno, _("cannot fstat %s"), quote (src_path)); return_val = -1; goto close_src_and_dst_desc; } /* If the file has fewer blocks than would normally be needed for a file of its size, then at least one of the blocks in the file is a hole. */ if (S_ISREG (sb.st_mode) && sb.st_size / ST_NBLOCKSIZE > ST_NBLOCKS (sb)) make_holes = 1; } #endif /* Make a buffer with space for a sentinel at the end. */ buf = (char *) alloca (buf_size + sizeof (int)); for (;;) { ssize_t n_read = read (source_desc, buf, buf_size); if (n_read < 0) { #ifdef EINTR if (errno == EINTR) continue; #endif error (0, errno, _("reading %s"), quote (src_path)); return_val = -1; goto close_src_and_dst_desc; } if (n_read == 0) break; n_read_total += n_read; ip = 0; if (make_holes) { buf[n_read] = 1; /* Sentinel to stop loop. */ /* Find first nonzero *word*, or the word with the sentinel. */ ip = (int *) buf; while (*ip++ == 0) ; /* Find the first nonzero *byte*, or the sentinel. */ cp = (char *) (ip - 1); while (*cp++ == 0) ; /* If we found the sentinel, the whole input block was zero, and we can make a hole. */ if (cp > buf + n_read) { /* Make a hole. */ if (lseek (dest_desc, (off_t) n_read, SEEK_CUR) < 0L) { error (0, errno, _("cannot lseek %s"), quote (dst_path)); return_val = -1; goto close_src_and_dst_desc; } last_write_made_hole = 1; } else /* Clear to indicate that a normal write is needed. */ ip = 0; } if (ip == 0) { size_t n = n_read; if (full_write (dest_desc, buf, n) != n) { error (0, errno, _("writing %s"), quote (dst_path)); return_val = -1; goto close_src_and_dst_desc; } last_write_made_hole = 0; } } /* If the file ends with a `hole', something needs to be written at the end. Otherwise the kernel would truncate the file at the end of the last write operation. */ if (last_write_made_hole) { #if HAVE_FTRUNCATE /* Write a null character and truncate it again. */ if (full_write (dest_desc, "", 1) != 1 || ftruncate (dest_desc, n_read_total) < 0) #else /* Seek backwards one character and write a null. */ if (lseek (dest_desc, (off_t) -1, SEEK_CUR) < 0L || full_write (dest_desc, "", 1) != 1) #endif { error (0, errno, _("writing %s"), quote (dst_path)); return_val = -1; } } close_src_and_dst_desc: if (close (dest_desc) < 0) { error (0, errno, _("closing %s"), quote (dst_path)); return_val = -1; } close_src_desc: if (close (source_desc) < 0) { error (0, errno, _("closing %s"), quote (src_path)); return_val = -1; } return return_val; }