OSDN Git Service

Merge branch 'master' of git://github.com/relan/exfat into marshmallow-x86
[android-x86/external-exfat.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 #if defined(__ANDROID__)
255         return lseek64(dev->fd, offset, whence);
256 #else
257         return lseek(dev->fd, offset, whence);
258 #endif
259 }
260
261 ssize_t exfat_read(struct exfat_dev* dev, void* buffer, size_t size)
262 {
263         return read(dev->fd, buffer, size);
264 }
265
266 ssize_t exfat_write(struct exfat_dev* dev, const void* buffer, size_t size)
267 {
268         return write(dev->fd, buffer, size);
269 }
270
271 ssize_t exfat_pread(struct exfat_dev* dev, void* buffer, size_t size,
272                 off_t offset)
273 {
274 #if defined(__ANDROID__)
275         return pread64(dev->fd, buffer, size, offset);
276 #else
277         return pread(dev->fd, buffer, size, offset);
278 #endif
279 }
280
281 ssize_t exfat_pwrite(struct exfat_dev* dev, const void* buffer, size_t size,
282                 off_t offset)
283 {
284 #if defined(__ANDROID__)
285         return pwrite64(dev->fd, buffer, size, offset);
286 #else
287         return pwrite(dev->fd, buffer, size, offset);
288 #endif
289 }
290
291 ssize_t exfat_generic_pread(const struct exfat* ef, struct exfat_node* node,
292                 void* buffer, size_t size, off_t offset)
293 {
294         cluster_t cluster;
295         char* bufp = buffer;
296         off_t lsize, loffset, remainder;
297
298         if (offset >= node->size)
299                 return 0;
300         if (size == 0)
301                 return 0;
302
303         cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
304         if (CLUSTER_INVALID(cluster))
305         {
306                 exfat_error("invalid cluster 0x%x while reading", cluster);
307                 return -EIO;
308         }
309
310         loffset = offset % CLUSTER_SIZE(*ef->sb);
311         remainder = MIN(size, node->size - offset);
312         while (remainder > 0)
313         {
314                 if (CLUSTER_INVALID(cluster))
315                 {
316                         exfat_error("invalid cluster 0x%x while reading", cluster);
317                         return -EIO;
318                 }
319                 lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
320                 if (exfat_pread(ef->dev, bufp, lsize,
321                                         exfat_c2o(ef, cluster) + loffset) < 0)
322                 {
323                         exfat_error("failed to read cluster %#x", cluster);
324                         return -EIO;
325                 }
326                 bufp += lsize;
327                 loffset = 0;
328                 remainder -= lsize;
329                 cluster = exfat_next_cluster(ef, node, cluster);
330         }
331         if (!(node->attrib & EXFAT_ATTRIB_DIR) && !ef->ro && !ef->noatime)
332                 exfat_update_atime(node);
333         return MIN(size, node->size - offset) - remainder;
334 }
335
336 ssize_t exfat_generic_pwrite(struct exfat* ef, struct exfat_node* node,
337                 const void* buffer, size_t size, off_t offset)
338 {
339         int rc;
340         cluster_t cluster;
341         const char* bufp = buffer;
342         off_t lsize, loffset, remainder;
343
344         if (offset > node->size)
345         {
346                 rc = exfat_truncate(ef, node, offset, true);
347                 if (rc != 0)
348                         return rc;
349         }
350         if (offset + size > node->size)
351         {
352                 rc = exfat_truncate(ef, node, offset + size, false);
353                 if (rc != 0)
354                         return rc;
355         }
356         if (size == 0)
357                 return 0;
358
359         cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
360         if (CLUSTER_INVALID(cluster))
361         {
362                 exfat_error("invalid cluster 0x%x while writing", cluster);
363                 return -EIO;
364         }
365
366         loffset = offset % CLUSTER_SIZE(*ef->sb);
367         remainder = size;
368         while (remainder > 0)
369         {
370                 if (CLUSTER_INVALID(cluster))
371                 {
372                         exfat_error("invalid cluster 0x%x while writing", cluster);
373                         return -EIO;
374                 }
375                 lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
376                 if (exfat_pwrite(ef->dev, bufp, lsize,
377                                 exfat_c2o(ef, cluster) + loffset) < 0)
378                 {
379                         exfat_error("failed to write cluster %#x", cluster);
380                         return -EIO;
381                 }
382                 bufp += lsize;
383                 loffset = 0;
384                 remainder -= lsize;
385                 cluster = exfat_next_cluster(ef, node, cluster);
386         }
387         if (!(node->attrib & EXFAT_ATTRIB_DIR))
388                 /* directory's mtime should be updated by the caller only when it
389                    creates or removes something in this directory */
390                 exfat_update_mtime(node);
391         return size - remainder;
392 }