blob: 306c5dd064f680a1f13cc596992d0e96743459b0 [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.
bigbiffc40c1c52014-11-01 09:34:57 -04006 Copyright (C) 2010-2013 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>
bigbiffc40c1c52014-11-01 09:34:57 -040027#include <sys/mount.h>
bigbiff bigbiff9c754052013-01-09 09:09:08 -050028#include <fcntl.h>
29#include <unistd.h>
30#include <string.h>
bigbiffc40c1c52014-11-01 09:34:57 -040031#ifdef __APPLE__
bigbiff bigbiff9c754052013-01-09 09:09:08 -050032#include <sys/disk.h>
33#endif
34#ifdef USE_UBLIO
35#include <sys/uio.h>
36#include <ublio.h>
37#endif
38
39struct exfat_dev
40{
41 int fd;
42 enum exfat_mode mode;
43 off64_t size; /* in bytes */
44#ifdef USE_UBLIO
45 off64_t pos;
46 ublio_filehandle_t ufh;
47#endif
48};
49
50static int open_ro(const char* spec)
51{
52 return open(spec, O_RDONLY);
53}
54
55static int open_rw(const char* spec)
56{
57 int fd = open(spec, O_RDWR);
58#ifdef __linux__
59 int ro = 0;
60
61 /*
62 This ioctl is needed because after "blockdev --setro" kernel still
63 allows to open the device in read-write mode but fails writes.
64 */
65 if (fd != -1 && ioctl(fd, BLKROGET, &ro) == 0 && ro)
66 {
67 close(fd);
68 return -1;
69 }
70#endif
71 return fd;
72}
73
74struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)
75{
76 struct exfat_dev* dev;
77 struct stat stbuf;
78#ifdef USE_UBLIO
79 struct ublio_param up;
80#endif
81
82 dev = malloc(sizeof(struct exfat_dev));
83 if (dev == NULL)
84 {
85 exfat_error("failed to allocate memory for device structure");
86 return NULL;
87 }
88
89 switch (mode)
90 {
91 case EXFAT_MODE_RO:
92 dev->fd = open_ro(spec);
93 if (dev->fd == -1)
94 {
95 free(dev);
bigbiffc40c1c52014-11-01 09:34:57 -040096 exfat_error("failed to open `%s' in read-only mode", spec);
bigbiff bigbiff9c754052013-01-09 09:09:08 -050097 return NULL;
98 }
99 dev->mode = EXFAT_MODE_RO;
100 break;
101 case EXFAT_MODE_RW:
102 dev->fd = open_rw(spec);
103 if (dev->fd == -1)
104 {
105 free(dev);
bigbiffc40c1c52014-11-01 09:34:57 -0400106 exfat_error("failed to open `%s' in read-write mode", spec);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500107 return NULL;
108 }
109 dev->mode = EXFAT_MODE_RW;
110 break;
111 case EXFAT_MODE_ANY:
112 dev->fd = open_rw(spec);
113 if (dev->fd != -1)
114 {
115 dev->mode = EXFAT_MODE_RW;
116 break;
117 }
118 dev->fd = open_ro(spec);
119 if (dev->fd != -1)
120 {
121 dev->mode = EXFAT_MODE_RO;
bigbiffc40c1c52014-11-01 09:34:57 -0400122 exfat_warn("`%s' is write-protected, mounting read-only", spec);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500123 break;
124 }
125 free(dev);
bigbiffc40c1c52014-11-01 09:34:57 -0400126 exfat_error("failed to open `%s'", spec);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500127 return NULL;
128 }
129
130 if (fstat(dev->fd, &stbuf) != 0)
131 {
132 close(dev->fd);
133 free(dev);
bigbiffc40c1c52014-11-01 09:34:57 -0400134 exfat_error("failed to fstat `%s'", spec);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500135 return NULL;
136 }
137 if (!S_ISBLK(stbuf.st_mode) &&
138 !S_ISCHR(stbuf.st_mode) &&
139 !S_ISREG(stbuf.st_mode))
140 {
141 close(dev->fd);
142 free(dev);
bigbiffc40c1c52014-11-01 09:34:57 -0400143 exfat_error("`%s' is neither a device, nor a regular file", spec);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500144 return NULL;
145 }
146
bigbiffc40c1c52014-11-01 09:34:57 -0400147#ifdef __APPLE__
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500148 if (!S_ISREG(stbuf.st_mode))
149 {
150 uint32_t block_size = 0;
151 uint64_t blocks = 0;
152
153 if (ioctl(dev->fd, DKIOCGETBLOCKSIZE, &block_size) != 0)
154 {
155 close(dev->fd);
156 free(dev);
157 exfat_error("failed to get block size");
158 return NULL;
159 }
160 if (ioctl(dev->fd, DKIOCGETBLOCKCOUNT, &blocks) != 0)
161 {
162 close(dev->fd);
163 free(dev);
164 exfat_error("failed to get blocks count");
165 return NULL;
166 }
167 dev->size = blocks * block_size;
168 }
169 else
170#endif
171 {
172 /* works for Linux, FreeBSD, Solaris */
173 dev->size = exfat_seek(dev, 0, SEEK_END);
174 if (dev->size <= 0)
175 {
176 close(dev->fd);
177 free(dev);
bigbiffc40c1c52014-11-01 09:34:57 -0400178 exfat_error("failed to get size of `%s'", spec);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500179 return NULL;
180 }
181 if (exfat_seek(dev, 0, SEEK_SET) == -1)
182 {
183 close(dev->fd);
184 free(dev);
bigbiffc40c1c52014-11-01 09:34:57 -0400185 exfat_error("failed to seek to the beginning of `%s'", spec);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500186 return NULL;
187 }
188 }
189
190#ifdef USE_UBLIO
191 memset(&up, 0, sizeof(struct ublio_param));
192 up.up_blocksize = 256 * 1024;
193 up.up_items = 64;
194 up.up_grace = 32;
195 up.up_priv = &dev->fd;
196
197 dev->pos = 0;
198 dev->ufh = ublio_open(&up);
199 if (dev->ufh == NULL)
200 {
201 close(dev->fd);
202 free(dev);
203 exfat_error("failed to initialize ublio");
204 return NULL;
205 }
206#endif
207
208 return dev;
209}
210
211int exfat_close(struct exfat_dev* dev)
212{
213#ifdef USE_UBLIO
214 if (ublio_close(dev->ufh) != 0)
215 exfat_error("failed to close ublio");
216#endif
217 if (close(dev->fd) != 0)
218 {
bigbiffc40c1c52014-11-01 09:34:57 -0400219 free(dev);
220 exfat_error("failed to close device");
221 return 1;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500222 }
223 free(dev);
bigbiffc40c1c52014-11-01 09:34:57 -0400224 return 0;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500225}
226
227int exfat_fsync(struct exfat_dev* dev)
228{
229#ifdef USE_UBLIO
230 if (ublio_fsync(dev->ufh) != 0)
bigbiffc40c1c52014-11-01 09:34:57 -0400231#else
bigbiff bigbiff2e33c5e2014-09-04 20:58:41 -0400232 if (fsync(dev->fd) != 0)
bigbiffc40c1c52014-11-01 09:34:57 -0400233#endif
bigbiff bigbiff2e33c5e2014-09-04 20:58:41 -0400234 {
bigbiffc40c1c52014-11-01 09:34:57 -0400235 exfat_error("fsync failed");
236 return 1;
bigbiff bigbiff2e33c5e2014-09-04 20:58:41 -0400237 }
bigbiffc40c1c52014-11-01 09:34:57 -0400238 return 0;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500239}
240
241enum exfat_mode exfat_get_mode(const struct exfat_dev* dev)
242{
243 return dev->mode;
244}
245
246off64_t exfat_get_size(const struct exfat_dev* dev)
247{
248 return dev->size;
249}
250
251off64_t exfat_seek(struct exfat_dev* dev, off64_t offset, int whence)
252{
253#ifdef USE_UBLIO
254 /* XXX SEEK_CUR will be handled incorrectly */
255 return dev->pos = lseek64(dev->fd, offset, whence);
256#else
257 return lseek64(dev->fd, offset, whence);
258#endif
259}
260
261ssize_t exfat_read(struct exfat_dev* dev, void* buffer, size_t size)
262{
263#ifdef USE_UBLIO
264 ssize_t result = ublio_pread(dev->ufh, buffer, size, dev->pos);
265 if (result >= 0)
266 dev->pos += size;
267 return result;
268#else
269 return read(dev->fd, buffer, size);
270#endif
271}
272
273ssize_t exfat_write(struct exfat_dev* dev, const void* buffer, size_t size)
274{
275#ifdef USE_UBLIO
276 ssize_t result = ublio_pwrite(dev->ufh, buffer, size, dev->pos);
277 if (result >= 0)
278 dev->pos += size;
279 return result;
280#else
281 return write(dev->fd, buffer, size);
282#endif
283}
284
bigbiff bigbiff61cdc022013-08-08 08:35:06 -0400285ssize_t exfat_pread(struct exfat_dev* dev, void* buffer, size_t size,
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500286 off64_t offset)
287{
288#ifdef USE_UBLIO
bigbiff bigbiff61cdc022013-08-08 08:35:06 -0400289 return ublio_pread(dev->ufh, buffer, size, offset);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500290#else
bigbiff bigbiff61cdc022013-08-08 08:35:06 -0400291 return pread64(dev->fd, buffer, size, offset);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500292#endif
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500293}
294
bigbiff bigbiff61cdc022013-08-08 08:35:06 -0400295ssize_t exfat_pwrite(struct exfat_dev* dev, const void* buffer, size_t size,
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500296 off64_t offset)
297{
298#ifdef USE_UBLIO
bigbiff bigbiff61cdc022013-08-08 08:35:06 -0400299 return ublio_pwrite(dev->ufh, buffer, size, offset);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500300#else
bigbiff bigbiff61cdc022013-08-08 08:35:06 -0400301 return pwrite64(dev->fd, buffer, size, offset);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500302#endif
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500303}
304
305ssize_t exfat_generic_pread(const struct exfat* ef, struct exfat_node* node,
306 void* buffer, size_t size, off64_t offset)
307{
308 cluster_t cluster;
309 char* bufp = buffer;
310 off64_t lsize, loffset, remainder;
311
312 if (offset >= node->size)
313 return 0;
314 if (size == 0)
315 return 0;
316
317 cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
318 if (CLUSTER_INVALID(cluster))
319 {
320 exfat_error("invalid cluster 0x%x while reading", cluster);
321 return -1;
322 }
323
324 loffset = offset % CLUSTER_SIZE(*ef->sb);
325 remainder = MIN(size, node->size - offset);
326 while (remainder > 0)
327 {
328 if (CLUSTER_INVALID(cluster))
329 {
330 exfat_error("invalid cluster 0x%x while reading", cluster);
331 return -1;
332 }
333 lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
bigbiff bigbiff61cdc022013-08-08 08:35:06 -0400334 if (exfat_pread(ef->dev, bufp, lsize,
335 exfat_c2o(ef, cluster) + loffset) < 0)
336 {
337 exfat_error("failed to read cluster %#x", cluster);
338 return -1;
339 }
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500340 bufp += lsize;
341 loffset = 0;
342 remainder -= lsize;
343 cluster = exfat_next_cluster(ef, node, cluster);
344 }
345 if (!ef->ro && !ef->noatime)
346 exfat_update_atime(node);
Dees_Troyb8fdac72013-01-25 19:42:52 +0000347 return MIN(size, node->size - offset) - remainder;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500348}
349
350ssize_t exfat_generic_pwrite(struct exfat* ef, struct exfat_node* node,
351 const void* buffer, size_t size, off64_t offset)
352{
353 cluster_t cluster;
354 const char* bufp = buffer;
355 off64_t lsize, loffset, remainder;
bigbiff bigbiff998716f2013-03-07 09:59:37 -0500356
357 if (offset > node->size)
358 if (exfat_truncate(ef, node, offset, true) != 0)
359 return -1;
360 if (offset + size > node->size)
361 if (exfat_truncate(ef, node, offset + size, false) != 0)
362 return -1;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500363 if (size == 0)
364 return 0;
365
366 cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
367 if (CLUSTER_INVALID(cluster))
368 {
369 exfat_error("invalid cluster 0x%x while writing", cluster);
370 return -1;
371 }
372
373 loffset = offset % CLUSTER_SIZE(*ef->sb);
374 remainder = size;
375 while (remainder > 0)
376 {
377 if (CLUSTER_INVALID(cluster))
378 {
379 exfat_error("invalid cluster 0x%x while writing", cluster);
380 return -1;
381 }
382 lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
bigbiff bigbiff61cdc022013-08-08 08:35:06 -0400383 if (exfat_pwrite(ef->dev, bufp, lsize,
384 exfat_c2o(ef, cluster) + loffset) < 0)
385 {
386 exfat_error("failed to write cluster %#x", cluster);
387 return -1;
388 }
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500389 bufp += lsize;
390 loffset = 0;
391 remainder -= lsize;
392 cluster = exfat_next_cluster(ef, node, cluster);
393 }
394 exfat_update_mtime(node);
395 return size - remainder;
396}