]> git.sven.stormbind.net Git - sven/exfat-utils.git/blobdiff - libexfat/io.c
New upstream version 1.3.0
[sven/exfat-utils.git] / libexfat / io.c
index 4413aaa52ce5b5256a5706a011e652368c0bfc6d..bc92c7cf9eab396eb465b93400cff7fc448a778b 100644 (file)
@@ -2,11 +2,12 @@
        io.c (02.09.09)
        exFAT file system implementation library.
 
        io.c (02.09.09)
        exFAT file system implementation library.
 
-       Copyright (C) 2010-2013  Andrew Nayenko
+       Free exFAT implementation.
+       Copyright (C) 2010-2018  Andrew Nayenko
 
 
-       This program is free software: you can redistribute it and/or modify
+       This program is free software; you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        it under the terms of the GNU General Public License as published by
-       the Free Software Foundation, either version 3 of the License, or
+       the Free Software Foundation, either version 2 of the License, or
        (at your option) any later version.
 
        This program is distributed in the hope that it will be useful,
        (at your option) any later version.
 
        This program is distributed in the hope that it will be useful,
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.
 
        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, see <http://www.gnu.org/licenses/>.
+       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.,
+       51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
 
 #include "exfat.h"
 #include <inttypes.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 */
 
 #include "exfat.h"
 #include <inttypes.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <sys/mount.h>
 #include <fcntl.h>
 #include <unistd.h>
 #include <string.h>
 #include <fcntl.h>
 #include <unistd.h>
 #include <string.h>
-#ifdef __APPLE__
+#include <errno.h>
+#if defined(__APPLE__)
 #include <sys/disk.h>
 #include <sys/disk.h>
+#elif defined(__OpenBSD__)
+#include <sys/param.h>
+#include <sys/disklabel.h>
+#include <sys/dkio.h>
+#include <sys/ioctl.h>
+#elif __linux__
+#include <sys/mount.h>
 #endif
 #ifdef USE_UBLIO
 #include <sys/uio.h>
 #endif
 #ifdef USE_UBLIO
 #include <sys/uio.h>
@@ -45,6 +54,11 @@ struct exfat_dev
 #endif
 };
 
 #endif
 };
 
+static bool is_open(int fd)
+{
+       return fcntl(fd, F_GETFD) != -1;
+}
+
 static int open_ro(const char* spec)
 {
        return open(spec, O_RDONLY);
 static int open_ro(const char* spec)
 {
        return open(spec, O_RDONLY);
@@ -63,6 +77,7 @@ static int open_rw(const char* spec)
        if (fd != -1 && ioctl(fd, BLKROGET, &ro) == 0 && ro)
        {
                close(fd);
        if (fd != -1 && ioctl(fd, BLKROGET, &ro) == 0 && ro)
        {
                close(fd);
+               errno = EROFS;
                return -1;
        }
 #endif
                return -1;
        }
 #endif
@@ -77,6 +92,24 @@ struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)
        struct ublio_param up;
 #endif
 
        struct ublio_param up;
 #endif
 
+       /* The system allocates file descriptors sequentially. If we have been
+          started with stdin (0), stdout (1) or stderr (2) closed, the system
+          will give us descriptor 0, 1 or 2 later when we open block device,
+          FUSE communication pipe, etc. As a result, functions using stdin,
+          stdout or stderr will actually work with a different thing and can
+          corrupt it. Protect descriptors 0, 1 and 2 from such misuse. */
+       while (!is_open(STDIN_FILENO)
+               || !is_open(STDOUT_FILENO)
+               || !is_open(STDERR_FILENO))
+       {
+               /* we don't need those descriptors, let them leak */
+               if (open("/dev/null", O_RDWR) == -1)
+               {
+                       exfat_error("failed to open /dev/null");
+                       return NULL;
+               }
+       }
+
        dev = malloc(sizeof(struct exfat_dev));
        if (dev == NULL)
        {
        dev = malloc(sizeof(struct exfat_dev));
        if (dev == NULL)
        {
@@ -91,7 +124,8 @@ struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)
                if (dev->fd == -1)
                {
                        free(dev);
                if (dev->fd == -1)
                {
                        free(dev);
-                       exfat_error("failed to open `%s' in read-only mode", spec);
+                       exfat_error("failed to open '%s' in read-only mode: %s", spec,
+                                       strerror(errno));
                        return NULL;
                }
                dev->mode = EXFAT_MODE_RO;
                        return NULL;
                }
                dev->mode = EXFAT_MODE_RO;
@@ -101,7 +135,8 @@ struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)
                if (dev->fd == -1)
                {
                        free(dev);
                if (dev->fd == -1)
                {
                        free(dev);
-                       exfat_error("failed to open `%s' in read-write mode", spec);
+                       exfat_error("failed to open '%s' in read-write mode: %s", spec,
+                                       strerror(errno));
                        return NULL;
                }
                dev->mode = EXFAT_MODE_RW;
                        return NULL;
                }
                dev->mode = EXFAT_MODE_RW;
@@ -117,11 +152,11 @@ struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)
                if (dev->fd != -1)
                {
                        dev->mode = EXFAT_MODE_RO;
                if (dev->fd != -1)
                {
                        dev->mode = EXFAT_MODE_RO;
-                       exfat_warn("`%s' is write-protected, mounting read-only", spec);
+                       exfat_warn("'%s' is write-protected, mounting read-only", spec);
                        break;
                }
                free(dev);
                        break;
                }
                free(dev);
-               exfat_error("failed to open `%s'", spec);
+               exfat_error("failed to open '%s': %s", spec, strerror(errno));
                return NULL;
        }
 
                return NULL;
        }
 
@@ -129,7 +164,7 @@ struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)
        {
                close(dev->fd);
                free(dev);
        {
                close(dev->fd);
                free(dev);
-               exfat_error("failed to fstat `%s'", spec);
+               exfat_error("failed to fstat '%s'", spec);
                return NULL;
        }
        if (!S_ISBLK(stbuf.st_mode) &&
                return NULL;
        }
        if (!S_ISBLK(stbuf.st_mode) &&
@@ -138,11 +173,11 @@ struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)
        {
                close(dev->fd);
                free(dev);
        {
                close(dev->fd);
                free(dev);
-               exfat_error("`%s' is neither a device, nor a regular file", spec);
+               exfat_error("'%s' is neither a device, nor a regular file", spec);
                return NULL;
        }
 
                return NULL;
        }
 
-#ifdef __APPLE__
+#if defined(__APPLE__)
        if (!S_ISREG(stbuf.st_mode))
        {
                uint32_t block_size = 0;
        if (!S_ISREG(stbuf.st_mode))
        {
                uint32_t block_size = 0;
@@ -165,6 +200,32 @@ struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)
                dev->size = blocks * block_size;
        }
        else
                dev->size = blocks * block_size;
        }
        else
+#elif defined(__OpenBSD__)
+       if (!S_ISREG(stbuf.st_mode))
+       {
+               struct disklabel lab;
+               struct partition* pp;
+               char* partition;
+
+               if (ioctl(dev->fd, DIOCGDINFO, &lab) == -1)
+               {
+                       close(dev->fd);
+                       free(dev);
+                       exfat_error("failed to get disklabel");
+                       return NULL;
+               }
+
+               /* Don't need to check that partition letter is valid as we won't get
+                  this far otherwise. */
+               partition = strchr(spec, '\0') - 1;
+               pp = &(lab.d_partitions[*partition - 'a']);
+               dev->size = DL_GETPSIZE(pp) * lab.d_secsize;
+
+               if (pp->p_fstype != FS_NTFS)
+                       exfat_warn("partition type is not 0x07 (NTFS/exFAT); "
+                                       "you can fix this with fdisk(8)");
+       }
+       else
 #endif
        {
                /* works for Linux, FreeBSD, Solaris */
 #endif
        {
                /* works for Linux, FreeBSD, Solaris */
@@ -173,14 +234,14 @@ struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)
                {
                        close(dev->fd);
                        free(dev);
                {
                        close(dev->fd);
                        free(dev);
-                       exfat_error("failed to get size of `%s'", spec);
+                       exfat_error("failed to get size of '%s'", spec);
                        return NULL;
                }
                if (exfat_seek(dev, 0, SEEK_SET) == -1)
                {
                        close(dev->fd);
                        free(dev);
                        return NULL;
                }
                if (exfat_seek(dev, 0, SEEK_SET) == -1)
                {
                        close(dev->fd);
                        free(dev);
-                       exfat_error("failed to seek to the beginning of `%s'", spec);
+                       exfat_error("failed to seek to the beginning of '%s'", spec);
                        return NULL;
                }
        }
                        return NULL;
                }
        }
@@ -208,32 +269,41 @@ struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)
 
 int exfat_close(struct exfat_dev* dev)
 {
 
 int exfat_close(struct exfat_dev* dev)
 {
+       int rc = 0;
+
 #ifdef USE_UBLIO
        if (ublio_close(dev->ufh) != 0)
 #ifdef USE_UBLIO
        if (ublio_close(dev->ufh) != 0)
+       {
                exfat_error("failed to close ublio");
                exfat_error("failed to close ublio");
+               rc = -EIO;
+       }
 #endif
        if (close(dev->fd) != 0)
        {
 #endif
        if (close(dev->fd) != 0)
        {
-               free(dev);
-               exfat_error("failed to close device");
-               return 1;
+               exfat_error("failed to close device: %s", strerror(errno));
+               rc = -EIO;
        }
        free(dev);
        }
        free(dev);
-       return 0;
+       return rc;
 }
 
 int exfat_fsync(struct exfat_dev* dev)
 {
 }
 
 int exfat_fsync(struct exfat_dev* dev)
 {
+       int rc = 0;
+
 #ifdef USE_UBLIO
        if (ublio_fsync(dev->ufh) != 0)
 #ifdef USE_UBLIO
        if (ublio_fsync(dev->ufh) != 0)
-#else
-       if (fsync(dev->fd) != 0)
+       {
+               exfat_error("ublio fsync failed");
+               rc = -EIO;
+       }
 #endif
 #endif
+       if (fsync(dev->fd) != 0)
        {
        {
-               exfat_error("fsync failed");
-               return 1;
+               exfat_error("fsync failed: %s", strerror(errno));
+               rc = -EIO;
        }
        }
-       return 0;
+       return rc;
 }
 
 enum exfat_mode exfat_get_mode(const struct exfat_dev* dev)
 }
 
 enum exfat_mode exfat_get_mode(const struct exfat_dev* dev)
@@ -280,28 +350,24 @@ ssize_t exfat_write(struct exfat_dev* dev, const void* buffer, size_t size)
 #endif
 }
 
 #endif
 }
 
-void exfat_pread(struct exfat_dev* dev, void* buffer, size_t size,
+ssize_t exfat_pread(struct exfat_dev* dev, void* buffer, size_t size,
                off_t offset)
 {
 #ifdef USE_UBLIO
                off_t offset)
 {
 #ifdef USE_UBLIO
-       if (ublio_pread(dev->ufh, buffer, size, offset) != size)
+       return ublio_pread(dev->ufh, buffer, size, offset);
 #else
 #else
-       if (pread(dev->fd, buffer, size, offset) != size)
+       return pread(dev->fd, buffer, size, offset);
 #endif
 #endif
-               exfat_bug("failed to read %zu bytes from file at %"PRIu64, size,
-                               (uint64_t) offset);
 }
 
 }
 
-void exfat_pwrite(struct exfat_dev* dev, const void* buffer, size_t size,
+ssize_t exfat_pwrite(struct exfat_dev* dev, const void* buffer, size_t size,
                off_t offset)
 {
 #ifdef USE_UBLIO
                off_t offset)
 {
 #ifdef USE_UBLIO
-       if (ublio_pwrite(dev->ufh, buffer, size, offset) != size)
+       return ublio_pwrite(dev->ufh, buffer, size, offset);
 #else
 #else
-       if (pwrite(dev->fd, buffer, size, offset) != size)
+       return pwrite(dev->fd, buffer, size, offset);
 #endif
 #endif
-               exfat_bug("failed to write %zu bytes to file at %"PRIu64, size,
-                               (uint64_t) offset);
 }
 
 ssize_t exfat_generic_pread(const struct exfat* ef, struct exfat_node* node,
 }
 
 ssize_t exfat_generic_pread(const struct exfat* ef, struct exfat_node* node,
@@ -317,29 +383,34 @@ ssize_t exfat_generic_pread(const struct exfat* ef, struct exfat_node* node,
                return 0;
 
        cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
                return 0;
 
        cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
-       if (CLUSTER_INVALID(cluster))
+       if (CLUSTER_INVALID(*ef->sb, cluster))
        {
                exfat_error("invalid cluster 0x%x while reading", cluster);
        {
                exfat_error("invalid cluster 0x%x while reading", cluster);
-               return -1;
+               return -EIO;
        }
 
        loffset = offset % CLUSTER_SIZE(*ef->sb);
        remainder = MIN(size, node->size - offset);
        while (remainder > 0)
        {
        }
 
        loffset = offset % CLUSTER_SIZE(*ef->sb);
        remainder = MIN(size, node->size - offset);
        while (remainder > 0)
        {
-               if (CLUSTER_INVALID(cluster))
+               if (CLUSTER_INVALID(*ef->sb, cluster))
                {
                        exfat_error("invalid cluster 0x%x while reading", cluster);
                {
                        exfat_error("invalid cluster 0x%x while reading", cluster);
-                       return -1;
+                       return -EIO;
                }
                lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
                }
                lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
-               exfat_pread(ef->dev, bufp, lsize, exfat_c2o(ef, cluster) + loffset);
+               if (exfat_pread(ef->dev, bufp, lsize,
+                                       exfat_c2o(ef, cluster) + loffset) < 0)
+               {
+                       exfat_error("failed to read cluster %#x", cluster);
+                       return -EIO;
+               }
                bufp += lsize;
                loffset = 0;
                remainder -= lsize;
                cluster = exfat_next_cluster(ef, node, cluster);
        }
                bufp += lsize;
                loffset = 0;
                remainder -= lsize;
                cluster = exfat_next_cluster(ef, node, cluster);
        }
-       if (!ef->ro && !ef->noatime)
+       if (!(node->attrib & EXFAT_ATTRIB_DIR) && !ef->ro && !ef->noatime)
                exfat_update_atime(node);
        return MIN(size, node->size - offset) - remainder;
 }
                exfat_update_atime(node);
        return MIN(size, node->size - offset) - remainder;
 }
@@ -347,39 +418,57 @@ ssize_t exfat_generic_pread(const struct exfat* ef, struct exfat_node* node,
 ssize_t exfat_generic_pwrite(struct exfat* ef, struct exfat_node* node,
                const void* buffer, size_t size, off_t offset)
 {
 ssize_t exfat_generic_pwrite(struct exfat* ef, struct exfat_node* node,
                const void* buffer, size_t size, off_t offset)
 {
+       int rc;
        cluster_t cluster;
        const char* bufp = buffer;
        off_t lsize, loffset, remainder;
 
        cluster_t cluster;
        const char* bufp = buffer;
        off_t lsize, loffset, remainder;
 
-       if (offset + size > node->size)
-               if (exfat_truncate(ef, node, offset + size) != 0)
-                       return -1;
+       if (offset > node->size)
+       {
+               rc = exfat_truncate(ef, node, offset, true);
+               if (rc != 0)
+                       return rc;
+       }
+       if (offset + size > node->size)
+       {
+               rc = exfat_truncate(ef, node, offset + size, false);
+               if (rc != 0)
+                       return rc;
+       }
        if (size == 0)
                return 0;
 
        cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
        if (size == 0)
                return 0;
 
        cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
-       if (CLUSTER_INVALID(cluster))
+       if (CLUSTER_INVALID(*ef->sb, cluster))
        {
                exfat_error("invalid cluster 0x%x while writing", cluster);
        {
                exfat_error("invalid cluster 0x%x while writing", cluster);
-               return -1;
+               return -EIO;
        }
 
        loffset = offset % CLUSTER_SIZE(*ef->sb);
        remainder = size;
        while (remainder > 0)
        {
        }
 
        loffset = offset % CLUSTER_SIZE(*ef->sb);
        remainder = size;
        while (remainder > 0)
        {
-               if (CLUSTER_INVALID(cluster))
+               if (CLUSTER_INVALID(*ef->sb, cluster))
                {
                        exfat_error("invalid cluster 0x%x while writing", cluster);
                {
                        exfat_error("invalid cluster 0x%x while writing", cluster);
-                       return -1;
+                       return -EIO;
                }
                lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
                }
                lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
-               exfat_pwrite(ef->dev, bufp, lsize, exfat_c2o(ef, cluster) + loffset);
+               if (exfat_pwrite(ef->dev, bufp, lsize,
+                               exfat_c2o(ef, cluster) + loffset) < 0)
+               {
+                       exfat_error("failed to write cluster %#x", cluster);
+                       return -EIO;
+               }
                bufp += lsize;
                loffset = 0;
                remainder -= lsize;
                cluster = exfat_next_cluster(ef, node, cluster);
        }
                bufp += lsize;
                loffset = 0;
                remainder -= lsize;
                cluster = exfat_next_cluster(ef, node, cluster);
        }
-       exfat_update_mtime(node);
+       if (!(node->attrib & EXFAT_ATTRIB_DIR))
+               /* directory's mtime should be updated by the caller only when it
+                  creates or removes something in this directory */
+               exfat_update_mtime(node);
        return size - remainder;
 }
        return size - remainder;
 }