OSDN Git Service

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