]> git.sven.stormbind.net Git - sven/exfat-utils.git/blob - libexfat/io.c
63ccfdbfdf9cc02e21cda76f38840bbdf9a7d751
[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-2017  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
41 struct exfat_dev
42 {
43         int fd;
44         enum exfat_mode mode;
45         off_t size; /* in bytes */
46 };
47
48 static bool is_open(int fd)
49 {
50         return fcntl(fd, F_GETFD) != -1;
51 }
52
53 static int open_ro(const char* spec)
54 {
55         return open(spec, O_RDONLY);
56 }
57
58 static int open_rw(const char* spec)
59 {
60         int fd = open(spec, O_RDWR);
61 #ifdef __linux__
62         int ro = 0;
63
64         /*
65            This ioctl is needed because after "blockdev --setro" kernel still
66            allows to open the device in read-write mode but fails writes.
67         */
68         if (fd != -1 && ioctl(fd, BLKROGET, &ro) == 0 && ro)
69         {
70                 close(fd);
71                 errno = EROFS;
72                 return -1;
73         }
74 #endif
75         return fd;
76 }
77
78 struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)
79 {
80         struct exfat_dev* dev;
81         struct stat stbuf;
82
83         /* The system allocates file descriptors sequentially. If we have been
84            started with stdin (0), stdout (1) or stderr (2) closed, the system
85            will give us descriptor 0, 1 or 2 later when we open block device,
86            FUSE communication pipe, etc. As a result, functions using stdin,
87            stdout or stderr will actualy work with a different thing and can
88            corrupt it. Protect descriptors 0, 1 and 2 from such misuse. */
89         while (!is_open(STDIN_FILENO)
90                 || !is_open(STDOUT_FILENO)
91                 || !is_open(STDERR_FILENO))
92         {
93                 /* we don't need those descriptors, let them leak */
94                 if (open("/dev/null", O_RDWR) == -1)
95                 {
96                         exfat_error("failed to open /dev/null");
97                         return NULL;
98                 }
99         }
100
101         dev = malloc(sizeof(struct exfat_dev));
102         if (dev == NULL)
103         {
104                 exfat_error("failed to allocate memory for device structure");
105                 return NULL;
106         }
107
108         switch (mode)
109         {
110         case EXFAT_MODE_RO:
111                 dev->fd = open_ro(spec);
112                 if (dev->fd == -1)
113                 {
114                         free(dev);
115                         exfat_error("failed to open '%s' in read-only mode: %s", spec,
116                                         strerror(errno));
117                         return NULL;
118                 }
119                 dev->mode = EXFAT_MODE_RO;
120                 break;
121         case EXFAT_MODE_RW:
122                 dev->fd = open_rw(spec);
123                 if (dev->fd == -1)
124                 {
125                         free(dev);
126                         exfat_error("failed to open '%s' in read-write mode: %s", spec,
127                                         strerror(errno));
128                         return NULL;
129                 }
130                 dev->mode = EXFAT_MODE_RW;
131                 break;
132         case EXFAT_MODE_ANY:
133                 dev->fd = open_rw(spec);
134                 if (dev->fd != -1)
135                 {
136                         dev->mode = EXFAT_MODE_RW;
137                         break;
138                 }
139                 dev->fd = open_ro(spec);
140                 if (dev->fd != -1)
141                 {
142                         dev->mode = EXFAT_MODE_RO;
143                         exfat_warn("'%s' is write-protected, mounting read-only", spec);
144                         break;
145                 }
146                 free(dev);
147                 exfat_error("failed to open '%s': %s", spec, strerror(errno));
148                 return NULL;
149         }
150
151         if (fstat(dev->fd, &stbuf) != 0)
152         {
153                 close(dev->fd);
154                 free(dev);
155                 exfat_error("failed to fstat '%s'", spec);
156                 return NULL;
157         }
158         if (!S_ISBLK(stbuf.st_mode) &&
159                 !S_ISCHR(stbuf.st_mode) &&
160                 !S_ISREG(stbuf.st_mode))
161         {
162                 close(dev->fd);
163                 free(dev);
164                 exfat_error("'%s' is neither a device, nor a regular file", spec);
165                 return NULL;
166         }
167
168 #if defined(__APPLE__)
169         if (!S_ISREG(stbuf.st_mode))
170         {
171                 uint32_t block_size = 0;
172                 uint64_t blocks = 0;
173
174                 if (ioctl(dev->fd, DKIOCGETBLOCKSIZE, &block_size) != 0)
175                 {
176                         close(dev->fd);
177                         free(dev);
178                         exfat_error("failed to get block size");
179                         return NULL;
180                 }
181                 if (ioctl(dev->fd, DKIOCGETBLOCKCOUNT, &blocks) != 0)
182                 {
183                         close(dev->fd);
184                         free(dev);
185                         exfat_error("failed to get blocks count");
186                         return NULL;
187                 }
188                 dev->size = blocks * block_size;
189         }
190         else
191 #elif defined(__OpenBSD__)
192         if (!S_ISREG(stbuf.st_mode))
193         {
194                 struct disklabel lab;
195                 struct partition* pp;
196                 char* partition;
197
198                 if (ioctl(dev->fd, DIOCGDINFO, &lab) == -1)
199                 {
200                         close(dev->fd);
201                         free(dev);
202                         exfat_error("failed to get disklabel");
203                         return NULL;
204                 }
205
206                 /* Don't need to check that partition letter is valid as we won't get
207                    this far otherwise. */
208                 partition = strchr(spec, '\0') - 1;
209                 pp = &(lab.d_partitions[*partition - 'a']);
210                 dev->size = DL_GETPSIZE(pp) * lab.d_secsize;
211
212                 if (pp->p_fstype != FS_NTFS)
213                         exfat_warn("partition type is not 0x07 (NTFS/exFAT); "
214                                         "you can fix this with fdisk(8)");
215         }
216         else
217 #endif
218         {
219                 /* works for Linux, FreeBSD, Solaris */
220                 dev->size = exfat_seek(dev, 0, SEEK_END);
221                 if (dev->size <= 0)
222                 {
223                         close(dev->fd);
224                         free(dev);
225                         exfat_error("failed to get size of '%s'", spec);
226                         return NULL;
227                 }
228                 if (exfat_seek(dev, 0, SEEK_SET) == -1)
229                 {
230                         close(dev->fd);
231                         free(dev);
232                         exfat_error("failed to seek to the beginning of '%s'", spec);
233                         return NULL;
234                 }
235         }
236
237         return dev;
238 }
239
240 int exfat_close(struct exfat_dev* dev)
241 {
242         int rc = 0;
243
244         if (close(dev->fd) != 0)
245         {
246                 exfat_error("failed to close device: %s", strerror(errno));
247                 rc = -EIO;
248         }
249         free(dev);
250         return rc;
251 }
252
253 int exfat_fsync(struct exfat_dev* dev)
254 {
255         int rc = 0;
256
257         if (fsync(dev->fd) != 0)
258         {
259                 exfat_error("fsync failed: %s", strerror(errno));
260                 rc = -EIO;
261         }
262         return rc;
263 }
264
265 enum exfat_mode exfat_get_mode(const struct exfat_dev* dev)
266 {
267         return dev->mode;
268 }
269
270 off_t exfat_get_size(const struct exfat_dev* dev)
271 {
272         return dev->size;
273 }
274
275 off_t exfat_seek(struct exfat_dev* dev, off_t offset, int whence)
276 {
277         return lseek(dev->fd, offset, whence);
278 }
279
280 ssize_t exfat_read(struct exfat_dev* dev, void* buffer, size_t size)
281 {
282         return read(dev->fd, buffer, size);
283 }
284
285 ssize_t exfat_write(struct exfat_dev* dev, const void* buffer, size_t size)
286 {
287         return write(dev->fd, buffer, size);
288 }
289
290 ssize_t exfat_pread(struct exfat_dev* dev, void* buffer, size_t size,
291                 off_t offset)
292 {
293         return pread(dev->fd, buffer, size, offset);
294 }
295
296 ssize_t exfat_pwrite(struct exfat_dev* dev, const void* buffer, size_t size,
297                 off_t offset)
298 {
299         return pwrite(dev->fd, buffer, size, offset);
300 }
301
302 ssize_t exfat_generic_pread(const struct exfat* ef, struct exfat_node* node,
303                 void* buffer, size_t size, off_t offset)
304 {
305         cluster_t cluster;
306         char* bufp = buffer;
307         off_t lsize, loffset, remainder;
308
309         if (offset >= node->size)
310                 return 0;
311         if (size == 0)
312                 return 0;
313
314         cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
315         if (CLUSTER_INVALID(*ef->sb, cluster))
316         {
317                 exfat_error("invalid cluster 0x%x while reading", cluster);
318                 return -EIO;
319         }
320
321         loffset = offset % CLUSTER_SIZE(*ef->sb);
322         remainder = MIN(size, node->size - offset);
323         while (remainder > 0)
324         {
325                 if (CLUSTER_INVALID(*ef->sb, cluster))
326                 {
327                         exfat_error("invalid cluster 0x%x while reading", cluster);
328                         return -EIO;
329                 }
330                 lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
331                 if (exfat_pread(ef->dev, bufp, lsize,
332                                         exfat_c2o(ef, cluster) + loffset) < 0)
333                 {
334                         exfat_error("failed to read cluster %#x", cluster);
335                         return -EIO;
336                 }
337                 bufp += lsize;
338                 loffset = 0;
339                 remainder -= lsize;
340                 cluster = exfat_next_cluster(ef, node, cluster);
341         }
342         if (!(node->attrib & EXFAT_ATTRIB_DIR) && !ef->ro && !ef->noatime)
343                 exfat_update_atime(node);
344         return MIN(size, node->size - offset) - remainder;
345 }
346
347 ssize_t exfat_generic_pwrite(struct exfat* ef, struct exfat_node* node,
348                 const void* buffer, size_t size, off_t offset)
349 {
350         int rc;
351         cluster_t cluster;
352         const char* bufp = buffer;
353         off_t lsize, loffset, remainder;
354
355         if (offset > node->size)
356         {
357                 rc = exfat_truncate(ef, node, offset, true);
358                 if (rc != 0)
359                         return rc;
360         }
361         if (offset + size > node->size)
362         {
363                 rc = exfat_truncate(ef, node, offset + size, false);
364                 if (rc != 0)
365                         return rc;
366         }
367         if (size == 0)
368                 return 0;
369
370         cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
371         if (CLUSTER_INVALID(*ef->sb, cluster))
372         {
373                 exfat_error("invalid cluster 0x%x while writing", cluster);
374                 return -EIO;
375         }
376
377         loffset = offset % CLUSTER_SIZE(*ef->sb);
378         remainder = size;
379         while (remainder > 0)
380         {
381                 if (CLUSTER_INVALID(*ef->sb, cluster))
382                 {
383                         exfat_error("invalid cluster 0x%x while writing", cluster);
384                         return -EIO;
385                 }
386                 lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
387                 if (exfat_pwrite(ef->dev, bufp, lsize,
388                                 exfat_c2o(ef, cluster) + loffset) < 0)
389                 {
390                         exfat_error("failed to write cluster %#x", cluster);
391                         return -EIO;
392                 }
393                 bufp += lsize;
394                 loffset = 0;
395                 remainder -= lsize;
396                 cluster = exfat_next_cluster(ef, node, cluster);
397         }
398         if (!(node->attrib & EXFAT_ATTRIB_DIR))
399                 /* directory's mtime should be updated by the caller only when it
400                    creates or removes something in this directory */
401                 exfat_update_mtime(node);
402         return size - remainder;
403 }