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