Imported Upstream version 1.2.1
[sven/exfat-utils.git] / libexfat / io.c
1 /*
2         io.c (02.09.09)
3         exFAT file system implementation library.
4
5         Free exFAT implementation.
6         Copyright (C) 2010-2015  Andrew Nayenko
7
8         This program is free software; you can redistribute it and/or modify
9         it under the terms of the GNU General Public License as published by
10         the Free Software Foundation, either version 2 of the License, or
11         (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
18         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.
21 */
22
23 #include "exfat.h"
24 #include <inttypes.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <fcntl.h>
28 #include <unistd.h>
29 #include <string.h>
30 #include <errno.h>
31 #if defined(__APPLE__)
32 #include <sys/disk.h>
33 #elif defined(__OpenBSD__)
34 #include <sys/param.h>
35 #include <sys/disklabel.h>
36 #include <sys/dkio.h>
37 #include <sys/ioctl.h>
38 #endif
39 #include <sys/mount.h>
40 #ifdef USE_UBLIO
41 #include <sys/uio.h>
42 #include <ublio.h>
43 #endif
44
45 struct exfat_dev
46 {
47         int fd;
48         enum exfat_mode mode;
49         off_t size; /* in bytes */
50 #ifdef USE_UBLIO
51         off_t pos;
52         ublio_filehandle_t ufh;
53 #endif
54 };
55
56 static int open_ro(const char* spec)
57 {
58         return open(spec, O_RDONLY);
59 }
60
61 static 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);
74                 errno = EROFS;
75                 return -1;
76         }
77 #endif
78         return fd;
79 }
80
81 struct 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);
103                         exfat_error("failed to open '%s' in read-only mode: %s", spec,
104                                         strerror(errno));
105                         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);
114                         exfat_error("failed to open '%s' in read-write mode: %s", spec,
115                                         strerror(errno));
116                         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;
131                         exfat_warn("'%s' is write-protected, mounting read-only", spec);
132                         break;
133                 }
134                 free(dev);
135                 exfat_error("failed to open '%s': %s", spec, strerror(errno));
136                 return NULL;
137         }
138
139         if (fstat(dev->fd, &stbuf) != 0)
140         {
141                 close(dev->fd);
142                 free(dev);
143                 exfat_error("failed to fstat '%s'", spec);
144                 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);
152                 exfat_error("'%s' is neither a device, nor a regular file", spec);
153                 return NULL;
154         }
155
156 #if defined(__APPLE__)
157         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
179 #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
205 #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);
213                         exfat_error("failed to get size of '%s'", spec);
214                         return NULL;
215                 }
216                 if (exfat_seek(dev, 0, SEEK_SET) == -1)
217                 {
218                         close(dev->fd);
219                         free(dev);
220                         exfat_error("failed to seek to the beginning of '%s'", spec);
221                         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
246 int exfat_close(struct exfat_dev* dev)
247 {
248         int rc = 0;
249
250 #ifdef USE_UBLIO
251         if (ublio_close(dev->ufh) != 0)
252         {
253                 exfat_error("failed to close ublio");
254                 rc = -EIO;
255         }
256 #endif
257         if (close(dev->fd) != 0)
258         {
259                 exfat_error("failed to close device: %s", strerror(errno));
260                 rc = -EIO;
261         }
262         free(dev);
263         return rc;
264 }
265
266 int exfat_fsync(struct exfat_dev* dev)
267 {
268         int rc = 0;
269
270 #ifdef USE_UBLIO
271         if (ublio_fsync(dev->ufh) != 0)
272         {
273                 exfat_error("ublio fsync failed");
274                 rc = -EIO;
275         }
276 #endif
277         if (fsync(dev->fd) != 0)
278         {
279                 exfat_error("fsync failed: %s", strerror(errno));
280                 rc = -EIO;
281         }
282         return rc;
283 }
284
285 enum exfat_mode exfat_get_mode(const struct exfat_dev* dev)
286 {
287         return dev->mode;
288 }
289
290 off_t exfat_get_size(const struct exfat_dev* dev)
291 {
292         return dev->size;
293 }
294
295 off_t exfat_seek(struct exfat_dev* dev, off_t offset, int whence)
296 {
297 #ifdef USE_UBLIO
298         /* XXX SEEK_CUR will be handled incorrectly */
299         return dev->pos = lseek(dev->fd, offset, whence);
300 #else
301         return lseek(dev->fd, offset, whence);
302 #endif
303 }
304
305 ssize_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
317 ssize_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
329 ssize_t exfat_pread(struct exfat_dev* dev, void* buffer, size_t size,
330                 off_t offset)
331 {
332 #ifdef USE_UBLIO
333         return ublio_pread(dev->ufh, buffer, size, offset);
334 #else
335         return pread(dev->fd, buffer, size, offset);
336 #endif
337 }
338
339 ssize_t exfat_pwrite(struct exfat_dev* dev, const void* buffer, size_t size,
340                 off_t offset)
341 {
342 #ifdef USE_UBLIO
343         return ublio_pwrite(dev->ufh, buffer, size, offset);
344 #else
345         return pwrite(dev->fd, buffer, size, offset);
346 #endif
347 }
348
349 ssize_t exfat_generic_pread(const struct exfat* ef, struct exfat_node* node,
350                 void* buffer, size_t size, off_t offset)
351 {
352         cluster_t cluster;
353         char* bufp = buffer;
354         off_t lsize, loffset, remainder;
355
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);
378                 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                 }
384                 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);
391         return MIN(size, node->size - offset) - remainder;
392 }
393
394 ssize_t exfat_generic_pwrite(struct exfat* ef, struct exfat_node* node,
395                 const void* buffer, size_t size, off_t offset)
396 {
397         cluster_t cluster;
398         const char* bufp = buffer;
399         off_t lsize, loffset, remainder;
400
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;
407         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);
427                 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                 }
433                 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 }