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