Whamcloud - gitweb
libext2fs: check for fallocate symbol before using it
[tools/e2fsprogs.git] / lib / ext2fs / unix_io.c
index 2302374..da3f8fd 100644 (file)
 
 #define _LARGEFILE_SOURCE
 #define _LARGEFILE64_SOURCE
+#ifndef _GNU_SOURCE
 #define _GNU_SOURCE
+#endif
 
+#include "config.h"
 #include <stdio.h>
 #include <string.h>
 #if HAVE_UNISTD_H
@@ -47,6 +50,9 @@
 #if HAVE_SYS_RESOURCE_H
 #include <sys/resource.h>
 #endif
+#if HAVE_LINUX_FALLOC_H
+#include <linux/falloc.h>
+#endif
 
 #if defined(__linux__) && defined(_IO) && !defined(BLKROGET)
 #define BLKROGET   _IO(0x12, 94) /* Get read-only status (0 = read_write).  */
@@ -159,12 +165,13 @@ static errcode_t unix_get_stats(io_channel channel, io_stats *stats)
 static errcode_t raw_read_blk(io_channel channel,
                              struct unix_private_data *data,
                              unsigned long long block,
-                             int count, void *buf)
+                             int count, void *bufv)
 {
        errcode_t       retval;
        ssize_t         size;
        ext2_loff_t     location;
        int             actual = 0;
+       unsigned char   *buf = bufv;
 
        size = (count < 0) ? -count : count * channel->block_size;
        data->io_stats.bytes_read += size;
@@ -219,12 +226,13 @@ error_out:
 static errcode_t raw_write_blk(io_channel channel,
                               struct unix_private_data *data,
                               unsigned long long block,
-                              int count, const void *buf)
+                              int count, const void *bufv)
 {
        ssize_t         size;
        ext2_loff_t     location;
        int             actual = 0;
        errcode_t       retval;
+       const unsigned char *buf = bufv;
 
        if (count == 1)
                size = channel->block_size;
@@ -425,13 +433,20 @@ static errcode_t flush_cached_blocks(io_channel channel,
 }
 #endif /* NO_IO_CACHE */
 
+#ifdef __linux__
+#ifndef BLKDISCARDZEROES
+#define BLKDISCARDZEROES _IO(0x12,124)
+#endif
+#endif
+
 static errcode_t unix_open(const char *name, int flags, io_channel *channel)
 {
        io_channel      io = NULL;
        struct unix_private_data *data = NULL;
        errcode_t       retval;
-       int             open_flags;
-       struct stat     st;
+       int             open_flags, zeroes = 0;
+       int             f_nocache = 0;
+       ext2fs_struct_stat st;
 #ifdef __linux__
        struct          utsname ut;
 #endif
@@ -440,7 +455,7 @@ static errcode_t unix_open(const char *name, int flags, io_channel *channel)
                return EXT2_ET_BAD_DEVICE_NAME;
        retval = ext2fs_get_mem(sizeof(struct struct_io_channel), &io);
        if (retval)
-               return retval;
+               goto cleanup;
        memset(io, 0, sizeof(struct struct_io_channel));
        io->magic = EXT2_ET_MAGIC_IO_CHANNEL;
        retval = ext2fs_get_mem(sizeof(struct unix_private_data), &data);
@@ -466,19 +481,40 @@ static errcode_t unix_open(const char *name, int flags, io_channel *channel)
        open_flags = (flags & IO_FLAG_RW) ? O_RDWR : O_RDONLY;
        if (flags & IO_FLAG_EXCLUSIVE)
                open_flags |= O_EXCL;
+#if defined(O_DIRECT)
        if (flags & IO_FLAG_DIRECT_IO)
                open_flags |= O_DIRECT;
+#elif defined(F_NOCACHE)
+       if (flags & IO_FLAG_DIRECT_IO)
+               f_nocache = F_NOCACHE;
+#endif
        data->flags = flags;
 
-#ifdef HAVE_OPEN64
-       data->dev = open64(io->name, open_flags);
-#else
-       data->dev = open(io->name, open_flags);
-#endif
+       data->dev = ext2fs_open_file(io->name, open_flags, 0);
        if (data->dev < 0) {
                retval = errno;
                goto cleanup;
        }
+       if (f_nocache) {
+               if (fcntl(data->dev, f_nocache, 1) < 0) {
+                       retval = errno;
+                       goto cleanup;
+               }
+       }
+
+       /*
+        * If the device is really a block device, then set the
+        * appropriate flag, otherwise we can set DISCARD_ZEROES flag
+        * because we are going to use punch hole instead of discard
+        * and if it succeed, subsequent read from sparse area returns
+        * zero.
+        */
+       if (ext2fs_stat(io->name, &st) == 0) {
+               if (S_ISBLK(st.st_mode))
+                       io->flags |= CHANNEL_FLAGS_BLOCK_DEVICE;
+               else
+                       io->flags |= CHANNEL_FLAGS_DISCARD_ZEROES;
+       }
 
 #ifdef BLKSSZGET
        if (flags & IO_FLAG_DIRECT_IO) {
@@ -487,6 +523,12 @@ static errcode_t unix_open(const char *name, int flags, io_channel *channel)
        }
 #endif
 
+#ifdef BLKDISCARDZEROES
+       ioctl(data->dev, BLKDISCARDZEROES, &zeroes);
+       if (zeroes)
+               io->flags |= CHANNEL_FLAGS_DISCARD_ZEROES;
+#endif
+
 #if defined(__CYGWIN__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
        /*
         * Some operating systems require that the buffers be aligned,
@@ -534,7 +576,7 @@ static errcode_t unix_open(const char *name, int flags, io_channel *channel)
             (ut.release[2] == '4') && (ut.release[3] == '.') &&
             (ut.release[4] == '1') && (ut.release[5] >= '0') &&
             (ut.release[5] < '8')) &&
-           (fstat(data->dev, &st) == 0) &&
+           (ext2fs_stat(io->name, &st) == 0) &&
            (S_ISBLK(st.st_mode))) {
                struct rlimit   rlim;
 
@@ -839,13 +881,12 @@ static errcode_t unix_set_option(io_channel channel, const char *option,
 }
 
 #if defined(__linux__) && !defined(BLKDISCARD)
-#define BLKDISCARD     _IO(0x12,119)
+#define BLKDISCARD             _IO(0x12,119)
 #endif
 
 static errcode_t unix_discard(io_channel channel, unsigned long long block,
                              unsigned long long count)
 {
-#ifdef BLKDISCARD
        struct unix_private_data *data;
        __uint64_t      range[2];
        int             ret;
@@ -854,14 +895,35 @@ static errcode_t unix_discard(io_channel channel, unsigned long long block,
        data = (struct unix_private_data *) channel->private_data;
        EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL);
 
-       range[0] = (__uint64_t)(block) * channel->block_size;
-       range[1] = (__uint64_t)(count) * channel->block_size;
+       if (channel->flags & CHANNEL_FLAGS_BLOCK_DEVICE) {
+#ifdef BLKDISCARD
+               range[0] = (__uint64_t)(block) * channel->block_size;
+               range[1] = (__uint64_t)(count) * channel->block_size;
 
-       ret = ioctl(data->dev, BLKDISCARD, &range);
-       if (ret < 0)
+               ret = ioctl(data->dev, BLKDISCARD, &range);
+#else
+               goto unimplemented;
+#endif
+       } else {
+#if defined(HAVE_FALLOCATE) && defined(FALLOC_FL_PUNCH_HOLE)
+               /*
+                * If we are not on block device, try to use punch hole
+                * to reclaim free space.
+                */
+               ret = fallocate(data->dev,
+                               FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+                               (off_t)(block) * channel->block_size,
+                               (off_t)(count) * channel->block_size);
+#else
+               goto unimplemented;
+#endif
+       }
+       if (ret < 0) {
+               if (errno == EOPNOTSUPP)
+                       goto unimplemented;
                return errno;
+       }
        return 0;
-#else
+unimplemented:
        return EXT2_ET_UNIMPLEMENTED;
-#endif
 }