Whamcloud - gitweb
libext2fs: check for fallocate symbol before using it
[tools/e2fsprogs.git] / lib / ext2fs / unix_io.c
index c1d0561..da3f8fd 100644 (file)
@@ -21,6 +21,7 @@
 #define _GNU_SOURCE
 #endif
 
+#include "config.h"
 #include <stdio.h>
 #include <string.h>
 #if HAVE_UNISTD_H
@@ -49,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).  */
@@ -441,7 +445,8 @@ static errcode_t unix_open(const char *name, int flags, io_channel *channel)
        struct unix_private_data *data = NULL;
        errcode_t       retval;
        int             open_flags, zeroes = 0;
-       struct stat     st;
+       int             f_nocache = 0;
+       ext2fs_struct_stat st;
 #ifdef __linux__
        struct          utsname ut;
 #endif
@@ -450,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);
@@ -476,21 +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;
-#ifdef O_DIRECT
+#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) {
@@ -552,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;
 
@@ -857,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;
@@ -872,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
 }