blob: 60aabb5ecdcf787376e3e31e9c1f053b13953c50 [file] [log] [blame]
bigbiff bigbiff9c754052013-01-09 09:09:08 -05001/*
2 io.c (02.09.09)
3 exFAT file system implementation library.
4
bigbiff bigbiff61cdc022013-08-08 08:35:06 -04005 Free exFAT implementation.
Matt Mower09ef1e42015-12-13 11:29:45 -06006 Copyright (C) 2010-2015 Andrew Nayenko
bigbiff bigbiff9c754052013-01-09 09:09:08 -05007
bigbiff bigbiff61cdc022013-08-08 08:35:06 -04008 This program is free software; you can redistribute it and/or modify
bigbiff bigbiff9c754052013-01-09 09:09:08 -05009 it under the terms of the GNU General Public License as published by
bigbiff bigbiff61cdc022013-08-08 08:35:06 -040010 the Free Software Foundation, either version 2 of the License, or
bigbiff bigbiff9c754052013-01-09 09:09:08 -050011 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
bigbiff bigbiff61cdc022013-08-08 08:35:06 -040018 You should have received a copy of the GNU General Public License along
19 with this program; if not, write to the Free Software Foundation, Inc.,
20 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
bigbiff bigbiff9c754052013-01-09 09:09:08 -050021*/
22
23#include "exfat.h"
24#include <inttypes.h>
25#include <sys/types.h>
26#include <sys/stat.h>
bigbiff bigbiff9c754052013-01-09 09:09:08 -050027#include <fcntl.h>
28#include <unistd.h>
29#include <string.h>
Matt Mower09ef1e42015-12-13 11:29:45 -060030#include <errno.h>
31#if defined(__APPLE__)
bigbiff bigbiff9c754052013-01-09 09:09:08 -050032#include <sys/disk.h>
Matt Mower09ef1e42015-12-13 11:29:45 -060033#elif defined(__OpenBSD__)
34#include <sys/param.h>
35#include <sys/disklabel.h>
36#include <sys/dkio.h>
37#include <sys/ioctl.h>
bigbiff bigbiff9c754052013-01-09 09:09:08 -050038#endif
Matt Mower09ef1e42015-12-13 11:29:45 -060039#include <sys/mount.h>
bigbiff bigbiff9c754052013-01-09 09:09:08 -050040#ifdef USE_UBLIO
41#include <sys/uio.h>
42#include <ublio.h>
43#endif
44
45struct exfat_dev
46{
47 int fd;
48 enum exfat_mode mode;
Spegeliusd69ac2b2014-11-23 15:15:06 +020049 loff_t size; /* in bytes */
bigbiff bigbiff9c754052013-01-09 09:09:08 -050050#ifdef USE_UBLIO
Spegeliusd69ac2b2014-11-23 15:15:06 +020051 loff_t pos;
bigbiff bigbiff9c754052013-01-09 09:09:08 -050052 ublio_filehandle_t ufh;
53#endif
54};
55
56static int open_ro(const char* spec)
57{
58 return open(spec, O_RDONLY);
59}
60
61static int open_rw(const char* spec)
62{
63 int fd = open(spec, O_RDWR);
64#ifdef __linux__
65 int ro = 0;
66
67 /*
68 This ioctl is needed because after "blockdev --setro" kernel still
69 allows to open the device in read-write mode but fails writes.
70 */
71 if (fd != -1 && ioctl(fd, BLKROGET, &ro) == 0 && ro)
72 {
73 close(fd);
Matt Mower09ef1e42015-12-13 11:29:45 -060074 errno = EROFS;
bigbiff bigbiff9c754052013-01-09 09:09:08 -050075 return -1;
76 }
77#endif
78 return fd;
79}
80
81struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)
82{
83 struct exfat_dev* dev;
84 struct stat stbuf;
85#ifdef USE_UBLIO
86 struct ublio_param up;
87#endif
88
89 dev = malloc(sizeof(struct exfat_dev));
90 if (dev == NULL)
91 {
92 exfat_error("failed to allocate memory for device structure");
93 return NULL;
94 }
95
96 switch (mode)
97 {
98 case EXFAT_MODE_RO:
99 dev->fd = open_ro(spec);
100 if (dev->fd == -1)
101 {
102 free(dev);
Matt Mower09ef1e42015-12-13 11:29:45 -0600103 exfat_error("failed to open '%s' in read-only mode: %s", spec,
104 strerror(errno));
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500105 return NULL;
106 }
107 dev->mode = EXFAT_MODE_RO;
108 break;
109 case EXFAT_MODE_RW:
110 dev->fd = open_rw(spec);
111 if (dev->fd == -1)
112 {
113 free(dev);
Matt Mower09ef1e42015-12-13 11:29:45 -0600114 exfat_error("failed to open '%s' in read-write mode: %s", spec,
115 strerror(errno));
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500116 return NULL;
117 }
118 dev->mode = EXFAT_MODE_RW;
119 break;
120 case EXFAT_MODE_ANY:
121 dev->fd = open_rw(spec);
122 if (dev->fd != -1)
123 {
124 dev->mode = EXFAT_MODE_RW;
125 break;
126 }
127 dev->fd = open_ro(spec);
128 if (dev->fd != -1)
129 {
130 dev->mode = EXFAT_MODE_RO;
Matt Mower09ef1e42015-12-13 11:29:45 -0600131 exfat_warn("'%s' is write-protected, mounting read-only", spec);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500132 break;
133 }
134 free(dev);
Matt Mower09ef1e42015-12-13 11:29:45 -0600135 exfat_error("failed to open '%s': %s", spec, strerror(errno));
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500136 return NULL;
137 }
138
139 if (fstat(dev->fd, &stbuf) != 0)
140 {
141 close(dev->fd);
142 free(dev);
Matt Mower09ef1e42015-12-13 11:29:45 -0600143 exfat_error("failed to fstat '%s'", spec);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500144 return NULL;
145 }
146 if (!S_ISBLK(stbuf.st_mode) &&
147 !S_ISCHR(stbuf.st_mode) &&
148 !S_ISREG(stbuf.st_mode))
149 {
150 close(dev->fd);
151 free(dev);
Matt Mower09ef1e42015-12-13 11:29:45 -0600152 exfat_error("'%s' is neither a device, nor a regular file", spec);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500153 return NULL;
154 }
155
Matt Mower09ef1e42015-12-13 11:29:45 -0600156#if defined(__APPLE__)
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500157 if (!S_ISREG(stbuf.st_mode))
158 {
159 uint32_t block_size = 0;
160 uint64_t blocks = 0;
161
162 if (ioctl(dev->fd, DKIOCGETBLOCKSIZE, &block_size) != 0)
163 {
164 close(dev->fd);
165 free(dev);
166 exfat_error("failed to get block size");
167 return NULL;
168 }
169 if (ioctl(dev->fd, DKIOCGETBLOCKCOUNT, &blocks) != 0)
170 {
171 close(dev->fd);
172 free(dev);
173 exfat_error("failed to get blocks count");
174 return NULL;
175 }
176 dev->size = blocks * block_size;
177 }
178 else
Matt Mower09ef1e42015-12-13 11:29:45 -0600179#elif defined(__OpenBSD__)
180 if (!S_ISREG(stbuf.st_mode))
181 {
182 struct disklabel lab;
183 struct partition* pp;
184 char* partition;
185
186 if (ioctl(dev->fd, DIOCGDINFO, &lab) == -1)
187 {
188 close(dev->fd);
189 free(dev);
190 exfat_error("failed to get disklabel");
191 return NULL;
192 }
193
194 /* Don't need to check that partition letter is valid as we won't get
195 this far otherwise. */
196 partition = strchr(spec, '\0') - 1;
197 pp = &(lab.d_partitions[*partition - 'a']);
198 dev->size = DL_GETPSIZE(pp) * lab.d_secsize;
199
200 if (pp->p_fstype != FS_NTFS)
201 exfat_warn("partition type is not 0x07 (NTFS/exFAT); "
202 "you can fix this with fdisk(8)");
203 }
204 else
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500205#endif
206 {
207 /* works for Linux, FreeBSD, Solaris */
208 dev->size = exfat_seek(dev, 0, SEEK_END);
209 if (dev->size <= 0)
210 {
211 close(dev->fd);
212 free(dev);
Matt Mower09ef1e42015-12-13 11:29:45 -0600213 exfat_error("failed to get size of '%s'", spec);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500214 return NULL;
215 }
216 if (exfat_seek(dev, 0, SEEK_SET) == -1)
217 {
218 close(dev->fd);
219 free(dev);
Matt Mower09ef1e42015-12-13 11:29:45 -0600220 exfat_error("failed to seek to the beginning of '%s'", spec);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500221 return NULL;
222 }
223 }
224
225#ifdef USE_UBLIO
226 memset(&up, 0, sizeof(struct ublio_param));
227 up.up_blocksize = 256 * 1024;
228 up.up_items = 64;
229 up.up_grace = 32;
230 up.up_priv = &dev->fd;
231
232 dev->pos = 0;
233 dev->ufh = ublio_open(&up);
234 if (dev->ufh == NULL)
235 {
236 close(dev->fd);
237 free(dev);
238 exfat_error("failed to initialize ublio");
239 return NULL;
240 }
241#endif
242
243 return dev;
244}
245
246int exfat_close(struct exfat_dev* dev)
247{
Matt Mower09ef1e42015-12-13 11:29:45 -0600248 int rc = 0;
249
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500250#ifdef USE_UBLIO
251 if (ublio_close(dev->ufh) != 0)
Matt Mower09ef1e42015-12-13 11:29:45 -0600252 {
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500253 exfat_error("failed to close ublio");
Matt Mower09ef1e42015-12-13 11:29:45 -0600254 rc = -EIO;
255 }
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500256#endif
257 if (close(dev->fd) != 0)
258 {
Matt Mower09ef1e42015-12-13 11:29:45 -0600259 exfat_error("failed to close device: %s", strerror(errno));
260 rc = -EIO;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500261 }
262 free(dev);
Matt Mower09ef1e42015-12-13 11:29:45 -0600263 return rc;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500264}
265
266int exfat_fsync(struct exfat_dev* dev)
267{
Matt Mower09ef1e42015-12-13 11:29:45 -0600268 int rc = 0;
269
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500270#ifdef USE_UBLIO
271 if (ublio_fsync(dev->ufh) != 0)
bigbiff bigbiff2e33c5e2014-09-04 20:58:41 -0400272 {
Matt Mower09ef1e42015-12-13 11:29:45 -0600273 exfat_error("ublio fsync failed");
274 rc = -EIO;
bigbiff bigbiff2e33c5e2014-09-04 20:58:41 -0400275 }
Matt Mower09ef1e42015-12-13 11:29:45 -0600276#endif
277 if (fsync(dev->fd) != 0)
278 {
279 exfat_error("fsync failed: %s", strerror(errno));
280 rc = -EIO;
281 }
282 return rc;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500283}
284
285enum exfat_mode exfat_get_mode(const struct exfat_dev* dev)
286{
287 return dev->mode;
288}
289
Spegeliusd69ac2b2014-11-23 15:15:06 +0200290loff_t exfat_get_size(const struct exfat_dev* dev)
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500291{
292 return dev->size;
293}
294
Spegeliusd69ac2b2014-11-23 15:15:06 +0200295loff_t exfat_seek(struct exfat_dev* dev, loff_t offset, int whence)
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500296{
297#ifdef USE_UBLIO
298 /* XXX SEEK_CUR will be handled incorrectly */
Matt Mower09ef1e42015-12-13 11:29:45 -0600299 return dev->pos = lseek(dev->fd, offset, whence);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500300#else
Andrey Alekseenkof5109882016-01-15 08:02:58 +0300301 return lseek64(dev->fd, offset, whence);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500302#endif
303}
304
305ssize_t exfat_read(struct exfat_dev* dev, void* buffer, size_t size)
306{
307#ifdef USE_UBLIO
308 ssize_t result = ublio_pread(dev->ufh, buffer, size, dev->pos);
309 if (result >= 0)
310 dev->pos += size;
311 return result;
312#else
313 return read(dev->fd, buffer, size);
314#endif
315}
316
317ssize_t exfat_write(struct exfat_dev* dev, const void* buffer, size_t size)
318{
319#ifdef USE_UBLIO
320 ssize_t result = ublio_pwrite(dev->ufh, buffer, size, dev->pos);
321 if (result >= 0)
322 dev->pos += size;
323 return result;
324#else
325 return write(dev->fd, buffer, size);
326#endif
327}
328
bigbiff bigbiff61cdc022013-08-08 08:35:06 -0400329ssize_t exfat_pread(struct exfat_dev* dev, void* buffer, size_t size,
Spegeliusd69ac2b2014-11-23 15:15:06 +0200330 loff_t offset)
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500331{
332#ifdef USE_UBLIO
bigbiff bigbiff61cdc022013-08-08 08:35:06 -0400333 return ublio_pread(dev->ufh, buffer, size, offset);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500334#else
Andrey Alekseenkof5109882016-01-15 08:02:58 +0300335 return pread64(dev->fd, buffer, size, offset);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500336#endif
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500337}
338
bigbiff bigbiff61cdc022013-08-08 08:35:06 -0400339ssize_t exfat_pwrite(struct exfat_dev* dev, const void* buffer, size_t size,
Spegeliusd69ac2b2014-11-23 15:15:06 +0200340 loff_t offset)
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500341{
342#ifdef USE_UBLIO
bigbiff bigbiff61cdc022013-08-08 08:35:06 -0400343 return ublio_pwrite(dev->ufh, buffer, size, offset);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500344#else
Andrey Alekseenkof5109882016-01-15 08:02:58 +0300345 return pwrite64(dev->fd, buffer, size, offset);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500346#endif
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500347}
348
349ssize_t exfat_generic_pread(const struct exfat* ef, struct exfat_node* node,
Spegeliusd69ac2b2014-11-23 15:15:06 +0200350 void* buffer, size_t size, loff_t offset)
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500351{
352 cluster_t cluster;
353 char* bufp = buffer;
Spegeliusd69ac2b2014-11-23 15:15:06 +0200354 loff_t lsize, loffset, remainder;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500355
356 if (offset >= node->size)
357 return 0;
358 if (size == 0)
359 return 0;
360
361 cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
362 if (CLUSTER_INVALID(cluster))
363 {
364 exfat_error("invalid cluster 0x%x while reading", cluster);
365 return -1;
366 }
367
368 loffset = offset % CLUSTER_SIZE(*ef->sb);
369 remainder = MIN(size, node->size - offset);
370 while (remainder > 0)
371 {
372 if (CLUSTER_INVALID(cluster))
373 {
374 exfat_error("invalid cluster 0x%x while reading", cluster);
375 return -1;
376 }
377 lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
bigbiff bigbiff61cdc022013-08-08 08:35:06 -0400378 if (exfat_pread(ef->dev, bufp, lsize,
379 exfat_c2o(ef, cluster) + loffset) < 0)
380 {
381 exfat_error("failed to read cluster %#x", cluster);
382 return -1;
383 }
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500384 bufp += lsize;
385 loffset = 0;
386 remainder -= lsize;
387 cluster = exfat_next_cluster(ef, node, cluster);
388 }
389 if (!ef->ro && !ef->noatime)
390 exfat_update_atime(node);
Dees_Troyb8fdac72013-01-25 19:42:52 +0000391 return MIN(size, node->size - offset) - remainder;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500392}
393
394ssize_t exfat_generic_pwrite(struct exfat* ef, struct exfat_node* node,
Spegeliusd69ac2b2014-11-23 15:15:06 +0200395 const void* buffer, size_t size, loff_t offset)
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500396{
397 cluster_t cluster;
398 const char* bufp = buffer;
Spegeliusd69ac2b2014-11-23 15:15:06 +0200399 loff_t lsize, loffset, remainder;
bigbiff bigbiff998716f2013-03-07 09:59:37 -0500400
401 if (offset > node->size)
402 if (exfat_truncate(ef, node, offset, true) != 0)
403 return -1;
404 if (offset + size > node->size)
405 if (exfat_truncate(ef, node, offset + size, false) != 0)
406 return -1;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500407 if (size == 0)
408 return 0;
409
410 cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
411 if (CLUSTER_INVALID(cluster))
412 {
413 exfat_error("invalid cluster 0x%x while writing", cluster);
414 return -1;
415 }
416
417 loffset = offset % CLUSTER_SIZE(*ef->sb);
418 remainder = size;
419 while (remainder > 0)
420 {
421 if (CLUSTER_INVALID(cluster))
422 {
423 exfat_error("invalid cluster 0x%x while writing", cluster);
424 return -1;
425 }
426 lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
bigbiff bigbiff61cdc022013-08-08 08:35:06 -0400427 if (exfat_pwrite(ef->dev, bufp, lsize,
428 exfat_c2o(ef, cluster) + loffset) < 0)
429 {
430 exfat_error("failed to write cluster %#x", cluster);
431 return -1;
432 }
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500433 bufp += lsize;
434 loffset = 0;
435 remainder -= lsize;
436 cluster = exfat_next_cluster(ef, node, cluster);
437 }
438 exfat_update_mtime(node);
439 return size - remainder;
440}