OSDN Git Service

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