OSDN Git Service

Fixed compilation on OpenBSD.
[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-2013  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                 return -1;
75         }
76 #endif
77         return fd;
78 }
79
80 struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)
81 {
82         struct exfat_dev* dev;
83         struct stat stbuf;
84 #ifdef USE_UBLIO
85         struct ublio_param up;
86 #endif
87
88         dev = malloc(sizeof(struct exfat_dev));
89         if (dev == NULL)
90         {
91                 exfat_error("failed to allocate memory for device structure");
92                 return NULL;
93         }
94
95         switch (mode)
96         {
97         case EXFAT_MODE_RO:
98                 dev->fd = open_ro(spec);
99                 if (dev->fd == -1)
100                 {
101                         free(dev);
102                         exfat_error("failed to open `%s' in read-only mode", spec);
103                         return NULL;
104                 }
105                 dev->mode = EXFAT_MODE_RO;
106                 break;
107         case EXFAT_MODE_RW:
108                 dev->fd = open_rw(spec);
109                 if (dev->fd == -1)
110                 {
111                         free(dev);
112                         exfat_error("failed to open `%s' in read-write mode", spec);
113                         return NULL;
114                 }
115                 dev->mode = EXFAT_MODE_RW;
116                 break;
117         case EXFAT_MODE_ANY:
118                 dev->fd = open_rw(spec);
119                 if (dev->fd != -1)
120                 {
121                         dev->mode = EXFAT_MODE_RW;
122                         break;
123                 }
124                 dev->fd = open_ro(spec);
125                 if (dev->fd != -1)
126                 {
127                         dev->mode = EXFAT_MODE_RO;
128                         exfat_warn("`%s' is write-protected, mounting read-only", spec);
129                         break;
130                 }
131                 free(dev);
132                 exfat_error("failed to open `%s'", spec);
133                 return NULL;
134         }
135
136         if (fstat(dev->fd, &stbuf) != 0)
137         {
138                 close(dev->fd);
139                 free(dev);
140                 exfat_error("failed to fstat `%s'", spec);
141                 return NULL;
142         }
143         if (!S_ISBLK(stbuf.st_mode) &&
144                 !S_ISCHR(stbuf.st_mode) &&
145                 !S_ISREG(stbuf.st_mode))
146         {
147                 close(dev->fd);
148                 free(dev);
149                 exfat_error("`%s' is neither a device, nor a regular file", spec);
150                 return NULL;
151         }
152
153 #if defined(__APPLE__)
154         if (!S_ISREG(stbuf.st_mode))
155         {
156                 uint32_t block_size = 0;
157                 uint64_t blocks = 0;
158
159                 if (ioctl(dev->fd, DKIOCGETBLOCKSIZE, &block_size) != 0)
160                 {
161                         close(dev->fd);
162                         free(dev);
163                         exfat_error("failed to get block size");
164                         return NULL;
165                 }
166                 if (ioctl(dev->fd, DKIOCGETBLOCKCOUNT, &blocks) != 0)
167                 {
168                         close(dev->fd);
169                         free(dev);
170                         exfat_error("failed to get blocks count");
171                         return NULL;
172                 }
173                 dev->size = blocks * block_size;
174         }
175         else
176 #elif defined(__OpenBSD__)
177         if (!S_ISREG(stbuf.st_mode))
178         {
179                 struct disklabel lab;
180                 struct partition* pp;
181                 char* partition;
182
183                 if (ioctl(dev->fd, DIOCGDINFO, &lab) == -1)
184                 {
185                         close(dev->fd);
186                         free(dev);
187                         exfat_error("failed to get disklabel");
188                         return NULL;
189                 }
190
191                 /* Don't need to check that partition letter is valid as we won't get
192                    this far otherwise. */
193                 partition = strchr(spec, '\0') - 1;
194                 pp = &(lab.d_partitions[*partition - 'a']);
195                 dev->size = DL_GETPSIZE(pp) * lab.d_secsize;
196
197                 if (pp->p_fstype != FS_NTFS)
198                         exfat_warn("partition type is not 0x07 (NTFS/exFAT); "
199                                         "you can fix this with fdisk(8)");
200         }
201         else
202 #endif
203         {
204                 /* works for Linux, FreeBSD, Solaris */
205                 dev->size = exfat_seek(dev, 0, SEEK_END);
206                 if (dev->size <= 0)
207                 {
208                         close(dev->fd);
209                         free(dev);
210                         exfat_error("failed to get size of `%s'", spec);
211                         return NULL;
212                 }
213                 if (exfat_seek(dev, 0, SEEK_SET) == -1)
214                 {
215                         close(dev->fd);
216                         free(dev);
217                         exfat_error("failed to seek to the beginning of `%s'", spec);
218                         return NULL;
219                 }
220         }
221
222 #ifdef USE_UBLIO
223         memset(&up, 0, sizeof(struct ublio_param));
224         up.up_blocksize = 256 * 1024;
225         up.up_items = 64;
226         up.up_grace = 32;
227         up.up_priv = &dev->fd;
228
229         dev->pos = 0;
230         dev->ufh = ublio_open(&up);
231         if (dev->ufh == NULL)
232         {
233                 close(dev->fd);
234                 free(dev);
235                 exfat_error("failed to initialize ublio");
236                 return NULL;
237         }
238 #endif
239
240         return dev;
241 }
242
243 int exfat_close(struct exfat_dev* dev)
244 {
245         int rc = 0;
246
247 #ifdef USE_UBLIO
248         if (ublio_close(dev->ufh) != 0)
249         {
250                 exfat_error("failed to close ublio");
251                 rc = -EIO;
252         }
253 #endif
254         if (close(dev->fd) != 0)
255         {
256                 exfat_error("failed to close device");
257                 rc = -EIO;
258         }
259         free(dev);
260         return rc;
261 }
262
263 int exfat_fsync(struct exfat_dev* dev)
264 {
265         int rc = 0;
266
267 #ifdef USE_UBLIO
268         if (ublio_fsync(dev->ufh) != 0)
269         {
270                 exfat_error("ublio fsync failed");
271                 rc = -EIO;
272         }
273 #endif
274         if (fsync(dev->fd) != 0)
275         {
276                 exfat_error("fsync failed");
277                 rc = -EIO;
278         }
279         return rc;
280 }
281
282 enum exfat_mode exfat_get_mode(const struct exfat_dev* dev)
283 {
284         return dev->mode;
285 }
286
287 off_t exfat_get_size(const struct exfat_dev* dev)
288 {
289         return dev->size;
290 }
291
292 off_t exfat_seek(struct exfat_dev* dev, off_t offset, int whence)
293 {
294 #ifdef USE_UBLIO
295         /* XXX SEEK_CUR will be handled incorrectly */
296         return dev->pos = lseek(dev->fd, offset, whence);
297 #else
298         return lseek(dev->fd, offset, whence);
299 #endif
300 }
301
302 ssize_t exfat_read(struct exfat_dev* dev, void* buffer, size_t size)
303 {
304 #ifdef USE_UBLIO
305         ssize_t result = ublio_pread(dev->ufh, buffer, size, dev->pos);
306         if (result >= 0)
307                 dev->pos += size;
308         return result;
309 #else
310         return read(dev->fd, buffer, size);
311 #endif
312 }
313
314 ssize_t exfat_write(struct exfat_dev* dev, const void* buffer, size_t size)
315 {
316 #ifdef USE_UBLIO
317         ssize_t result = ublio_pwrite(dev->ufh, buffer, size, dev->pos);
318         if (result >= 0)
319                 dev->pos += size;
320         return result;
321 #else
322         return write(dev->fd, buffer, size);
323 #endif
324 }
325
326 ssize_t exfat_pread(struct exfat_dev* dev, void* buffer, size_t size,
327                 off_t offset)
328 {
329 #ifdef USE_UBLIO
330         return ublio_pread(dev->ufh, buffer, size, offset);
331 #else
332         return pread(dev->fd, buffer, size, offset);
333 #endif
334 }
335
336 ssize_t exfat_pwrite(struct exfat_dev* dev, const void* buffer, size_t size,
337                 off_t offset)
338 {
339 #ifdef USE_UBLIO
340         return ublio_pwrite(dev->ufh, buffer, size, offset);
341 #else
342         return pwrite(dev->fd, buffer, size, offset);
343 #endif
344 }
345
346 ssize_t exfat_generic_pread(const struct exfat* ef, struct exfat_node* node,
347                 void* buffer, size_t size, off_t offset)
348 {
349         cluster_t cluster;
350         char* bufp = buffer;
351         off_t lsize, loffset, remainder;
352
353         if (offset >= node->size)
354                 return 0;
355         if (size == 0)
356                 return 0;
357
358         cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
359         if (CLUSTER_INVALID(cluster))
360         {
361                 exfat_error("invalid cluster 0x%x while reading", cluster);
362                 return -1;
363         }
364
365         loffset = offset % CLUSTER_SIZE(*ef->sb);
366         remainder = MIN(size, node->size - offset);
367         while (remainder > 0)
368         {
369                 if (CLUSTER_INVALID(cluster))
370                 {
371                         exfat_error("invalid cluster 0x%x while reading", cluster);
372                         return -1;
373                 }
374                 lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
375                 if (exfat_pread(ef->dev, bufp, lsize,
376                                         exfat_c2o(ef, cluster) + loffset) < 0)
377                 {
378                         exfat_error("failed to read cluster %#x", cluster);
379                         return -1;
380                 }
381                 bufp += lsize;
382                 loffset = 0;
383                 remainder -= lsize;
384                 cluster = exfat_next_cluster(ef, node, cluster);
385         }
386         if (!ef->ro && !ef->noatime)
387                 exfat_update_atime(node);
388         return MIN(size, node->size - offset) - remainder;
389 }
390
391 ssize_t exfat_generic_pwrite(struct exfat* ef, struct exfat_node* node,
392                 const void* buffer, size_t size, off_t offset)
393 {
394         cluster_t cluster;
395         const char* bufp = buffer;
396         off_t lsize, loffset, remainder;
397
398         if (offset > node->size)
399                 if (exfat_truncate(ef, node, offset, true) != 0)
400                         return -1;
401         if (offset + size > node->size)
402                 if (exfat_truncate(ef, node, offset + size, false) != 0)
403                         return -1;
404         if (size == 0)
405                 return 0;
406
407         cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
408         if (CLUSTER_INVALID(cluster))
409         {
410                 exfat_error("invalid cluster 0x%x while writing", cluster);
411                 return -1;
412         }
413
414         loffset = offset % CLUSTER_SIZE(*ef->sb);
415         remainder = size;
416         while (remainder > 0)
417         {
418                 if (CLUSTER_INVALID(cluster))
419                 {
420                         exfat_error("invalid cluster 0x%x while writing", cluster);
421                         return -1;
422                 }
423                 lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
424                 if (exfat_pwrite(ef->dev, bufp, lsize,
425                                 exfat_c2o(ef, cluster) + loffset) < 0)
426                 {
427                         exfat_error("failed to write cluster %#x", cluster);
428                         return -1;
429                 }
430                 bufp += lsize;
431                 loffset = 0;
432                 remainder -= lsize;
433                 cluster = exfat_next_cluster(ef, node, cluster);
434         }
435         exfat_update_mtime(node);
436         return size - remainder;
437 }