+/*
+ * Copyright (C) 2011 Oracle. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 021110-1307, USA.
+ */
+#define _XOPEN_SOURCE 500
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+
+#ifndef SEEK_DATA
+#define SEEK_DATA 3
+#define SEEK_HOLE 4
+#endif
+
+#define BUF_SIZE 4096
+#ifndef MIN
+#define MIN(a,b) ((a)<(b)?(a):(b))
+#endif
+
+static void
+error(const char *fmt, ...)
+{
+ char buf[256];
+ va_list args;
+ va_start(args, fmt);
+ vsprintf(buf, fmt, args);
+ va_end(args);
+
+ fprintf(stderr, "ERROR: [%s:%d] %s:%s\n", __func__, __LINE__,
+ buf, strerror(errno));
+}
+
+static size_t
+full_write(int fd, const void *buf, size_t count)
+{
+ size_t total = 0;
+ const char *ptr = (const char *) buf;
+
+ while (count > 0) {
+ ssize_t n = write(fd, ptr, count);
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ error("failed as %s", strerror(errno));
+ break;
+ }
+
+ if (n == 0) {
+ error("%zu bytes transferred. Aborting.",
+ total);
+ break;
+ }
+
+ total += n;
+ ptr += n;
+ count -= n;
+ }
+
+ return total;
+}
+
+/*
+ * Copy a data extent from source file to dest file.
+ * @data_off: data offset
+ * @hole_off: hole offset
+ * The length of this extent is (hole_off - data_off).
+ */
+static int
+do_extent_copy(int src_fd, int dest_fd, off_t data_off, off_t hole_off)
+{
+ uint64_t len = (uint64_t)(hole_off - data_off);
+ char buf[BUF_SIZE];
+ int ret;
+
+ /* Seek to data_off for data reading */
+ ret = lseek(src_fd, data_off, SEEK_SET);
+ if (ret < 0) {
+ error("seek source file to %llu failed as %s",
+ (uint64_t)data_off, strerror(errno));
+ return ret;
+ }
+
+ /* Seek to data_off for data writing, make holes as well */
+ ret = lseek(dest_fd, data_off, SEEK_SET);
+ if (ret < 0) {
+ error("seek dest file to %llu failed as %s",
+ (uint64_t)data_off, strerror(errno));
+ return ret;
+ }
+
+ while (len > 0) {
+ ssize_t nr_read = read(src_fd, buf, BUF_SIZE);
+ if (nr_read < 0) {
+ if (errno == EINTR)
+ continue;
+ error("read source file extent failed as %s",
+ strerror(errno));
+ ret = -1;
+ break;
+ }
+
+ if (nr_read == 0) {
+ error("reached EOF");
+ break;
+ }
+
+ if (full_write(dest_fd, buf, nr_read) != nr_read) {
+ error("write data to dest file failed as %s",
+ strerror(errno));
+ ret = -1;
+ break;
+ }
+
+ len -= nr_read;
+ }
+
+ return ret;
+}
+
+/*
+ * If lseek(2) failed and the errno is set to ENXIO, for
+ * SEEK_DATA there are no more data regions past the supplied
+ * offset. For SEEK_HOLE, there are no more holes past the
+ * supplied offset. Set scan->hit_final_extent to true for
+ * either case.
+ */
+static int
+copy_extents(int src_fd, int dest_fd, off_t src_total_size)
+{
+ int ret = 0;
+ off_t seek_start = 0;
+ off_t dest_pos = 0;
+ off_t data_pos, hole_pos;
+
+ do {
+ data_pos = lseek(src_fd, seek_start, SEEK_DATA);
+ if (data_pos < 0) {
+ if (errno == ENXIO)
+ ret = 0;
+ else {
+ error("SEEK_DATA failed due to %s",
+ strerror(errno));
+ ret = -1;
+ }
+ break;
+ }
+
+ hole_pos = lseek(src_fd, data_pos, SEEK_HOLE);
+ if (hole_pos < 0) {
+ if (errno == ENXIO)
+ ret = 0;
+ else {
+ error("SEEK_HOLE failed due to %s\n",
+ strerror(errno));
+ ret = -1;
+ }
+ break;
+ }
+
+ /* do extent copy */
+ ret = do_extent_copy(src_fd, dest_fd, data_pos, hole_pos);
+ if (ret < 0) {
+ error("copy extent failed");
+ break;
+ }
+
+ dest_pos += (hole_pos - data_pos);
+ seek_start = hole_pos;
+ } while (seek_start < src_total_size);
+
+ if (dest_pos < src_total_size) {
+ ret = ftruncate(dest_fd, src_total_size);
+ if (ret < 0) {
+ error("truncate dest file to %lld bytes failed as %s",
+ (long long)src_total_size, strerror(errno));
+ }
+ }
+
+ return ret;
+}
+
+int
+main(int argc, char **argv)
+{
+ int ret = 0;
+ int src_fd;
+ int dest_fd;
+ struct stat st;
+ size_t src_total_size;
+
+ if (argc != 3) {
+ fprintf(stdout, "Usage: %s source dest\n", argv[0]);
+ return 1;
+ }
+
+ src_fd = open(argv[1], O_RDONLY, 0644);
+ if (src_fd < 0) {
+ error("create %s failed", argv[1]);
+ return -1;
+ }
+
+ dest_fd = open(argv[2], O_RDWR|O_CREAT|O_EXCL, 0644);
+ if (dest_fd < 0) {
+ error("create %s failed", argv[2]);
+ ret = -errno;
+ goto close_src_fd;
+ }
+
+ ret = fstat(src_fd, &st);
+ if (ret < 0) {
+ error("get file %s staticis failed", argv[1]);
+ ret = -errno;
+ goto close_dest_fd;
+ }
+
+ src_total_size = st.st_size;
+ ret = copy_extents(src_fd, dest_fd, src_total_size);
+ if (ret < 0)
+ error("extents copy failed");
+
+close_dest_fd:
+ close(dest_fd);
+close_src_fd:
+ close(src_fd);
+
+ return ret;
+}