OSDN Git Service

Handle 64-bit offsets correctly on Android
[android-x86/external-exfat.git] / libexfat / io.c
1 /*
2         io.c (02.09.09)
3         exFAT file system implementation library.
4
5         Copyright (C) 2010-2013  Andrew Nayenko
6
7         This program is free software: you can redistribute it and/or modify
8         it under the terms of the GNU General Public License as published by
9         the Free Software Foundation, either version 3 of the License, or
10         (at your option) any later version.
11
12         This program is distributed in the hope that it will be useful,
13         but WITHOUT ANY WARRANTY; without even the implied warranty of
14         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15         GNU General Public License for more details.
16
17         You should have received a copy of the GNU General Public License
18         along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "exfat.h"
22 #include <inttypes.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <sys/mount.h>
26 #include <fcntl.h>
27 #include <unistd.h>
28 #include <string.h>
29 #ifdef __APPLE__
30 #include <sys/disk.h>
31 #endif
32 #ifdef USE_UBLIO
33 #include <sys/uio.h>
34 #include <ublio.h>
35 #endif
36
37 struct exfat_dev
38 {
39         int fd;
40         enum exfat_mode mode;
41         off_t size; /* in bytes */
42 #ifdef USE_UBLIO
43         off_t pos;
44         ublio_filehandle_t ufh;
45 #endif
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                 return -1;
67         }
68 #endif
69         return fd;
70 }
71
72 struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)
73 {
74         struct exfat_dev* dev;
75         struct stat stbuf;
76 #ifdef USE_UBLIO
77         struct ublio_param up;
78 #endif
79
80         dev = malloc(sizeof(struct exfat_dev));
81         if (dev == NULL)
82         {
83                 exfat_error("failed to allocate memory for device structure");
84                 return NULL;
85         }
86
87         switch (mode)
88         {
89         case EXFAT_MODE_RO:
90                 dev->fd = open_ro(spec);
91                 if (dev->fd == -1)
92                 {
93                         free(dev);
94                         exfat_error("failed to open `%s' in read-only mode", spec);
95                         return NULL;
96                 }
97                 dev->mode = EXFAT_MODE_RO;
98                 break;
99         case EXFAT_MODE_RW:
100                 dev->fd = open_rw(spec);
101                 if (dev->fd == -1)
102                 {
103                         free(dev);
104                         exfat_error("failed to open `%s' in read-write mode", spec);
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'", spec);
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 #ifdef __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 #endif
169         {
170                 /* works for Linux, FreeBSD, Solaris */
171                 dev->size = exfat_seek(dev, 0, SEEK_END);
172                 if (dev->size <= 0)
173                 {
174                         close(dev->fd);
175                         free(dev);
176                         exfat_error("failed to get size of `%s'", spec);
177                         return NULL;
178                 }
179                 if (exfat_seek(dev, 0, SEEK_SET) == -1)
180                 {
181                         close(dev->fd);
182                         free(dev);
183                         exfat_error("failed to seek to the beginning of `%s'", spec);
184                         return NULL;
185                 }
186         }
187
188 #ifdef USE_UBLIO
189         memset(&up, 0, sizeof(struct ublio_param));
190         up.up_blocksize = 256 * 1024;
191         up.up_items = 64;
192         up.up_grace = 32;
193         up.up_priv = &dev->fd;
194
195         dev->pos = 0;
196         dev->ufh = ublio_open(&up);
197         if (dev->ufh == NULL)
198         {
199                 close(dev->fd);
200                 free(dev);
201                 exfat_error("failed to initialize ublio");
202                 return NULL;
203         }
204 #endif
205
206         return dev;
207 }
208
209 int exfat_close(struct exfat_dev* dev)
210 {
211 #ifdef USE_UBLIO
212         if (ublio_close(dev->ufh) != 0)
213                 exfat_error("failed to close ublio");
214 #endif
215         if (close(dev->fd) != 0)
216         {
217                 free(dev);
218                 exfat_error("failed to close device");
219                 return 1;
220         }
221         free(dev);
222         return 0;
223 }
224
225 int exfat_fsync(struct exfat_dev* dev)
226 {
227 #ifdef USE_UBLIO
228         if (ublio_fsync(dev->ufh) != 0)
229 #else
230         if (fsync(dev->fd) != 0)
231 #endif
232         {
233                 exfat_error("fsync failed");
234                 return 1;
235         }
236         return 0;
237 }
238
239 enum exfat_mode exfat_get_mode(const struct exfat_dev* dev)
240 {
241         return dev->mode;
242 }
243
244 off_t exfat_get_size(const struct exfat_dev* dev)
245 {
246         return dev->size;
247 }
248
249 off_t exfat_seek(struct exfat_dev* dev, off_t offset, int whence)
250 {
251 #ifdef USE_UBLIO
252         /* XXX SEEK_CUR will be handled incorrectly */
253         return dev->pos = lseek(dev->fd, offset, whence);
254 #elif 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 #ifdef USE_UBLIO
264         ssize_t result = ublio_pread(dev->ufh, buffer, size, dev->pos);
265         if (result >= 0)
266                 dev->pos += size;
267         return result;
268 #else
269         return read(dev->fd, buffer, size);
270 #endif
271 }
272
273 ssize_t exfat_write(struct exfat_dev* dev, const void* buffer, size_t size)
274 {
275 #ifdef USE_UBLIO
276         ssize_t result = ublio_pwrite(dev->ufh, buffer, size, dev->pos);
277         if (result >= 0)
278                 dev->pos += size;
279         return result;
280 #else
281         return write(dev->fd, buffer, size);
282 #endif
283 }
284
285 void exfat_pread(struct exfat_dev* dev, void* buffer, size_t size,
286                 off_t offset)
287 {
288 #ifdef USE_UBLIO
289         if (ublio_pread(dev->ufh, buffer, size, offset) != size)
290 #elif defined(__ANDROID__)
291     if (pread64(dev->fd, buffer, size, offset) != size)
292 #else
293         if (pread(dev->fd, buffer, size, offset) != size)
294 #endif
295                 exfat_bug("failed to read %zu bytes from file at %"PRIu64, size,
296                                 (uint64_t) offset);
297 }
298
299 void exfat_pwrite(struct exfat_dev* dev, const void* buffer, size_t size,
300                 off_t offset)
301 {
302 #ifdef USE_UBLIO
303         if (ublio_pwrite(dev->ufh, buffer, size, offset) != size)
304 #elif defined(__ANDROID__)
305     if (pwrite64(dev->fd, buffer, size, offset) != size)
306 #else
307         if (pwrite(dev->fd, buffer, size, offset) != size)
308 #endif
309                 exfat_bug("failed to write %zu bytes to file at %"PRIu64, size,
310                                 (uint64_t) offset);
311 }
312
313 ssize_t exfat_generic_pread(const struct exfat* ef, struct exfat_node* node,
314                 void* buffer, size_t size, off_t offset)
315 {
316         cluster_t cluster;
317         char* bufp = buffer;
318         off_t lsize, loffset, remainder;
319
320         if (offset >= node->size)
321                 return 0;
322         if (size == 0)
323                 return 0;
324
325         cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
326         if (CLUSTER_INVALID(cluster))
327         {
328                 exfat_error("invalid cluster 0x%x while reading", cluster);
329                 return -1;
330         }
331
332         loffset = offset % CLUSTER_SIZE(*ef->sb);
333         remainder = MIN(size, node->size - offset);
334         while (remainder > 0)
335         {
336                 if (CLUSTER_INVALID(cluster))
337                 {
338                         exfat_error("invalid cluster 0x%x while reading", cluster);
339                         return -1;
340                 }
341                 lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
342                 exfat_pread(ef->dev, bufp, lsize, exfat_c2o(ef, cluster) + loffset);
343                 bufp += lsize;
344                 loffset = 0;
345                 remainder -= lsize;
346                 cluster = exfat_next_cluster(ef, node, cluster);
347         }
348         if (!ef->ro && !ef->noatime)
349                 exfat_update_atime(node);
350         return MIN(size, node->size - offset) - remainder;
351 }
352
353 ssize_t exfat_generic_pwrite(struct exfat* ef, struct exfat_node* node,
354                 const void* buffer, size_t size, off_t offset)
355 {
356         cluster_t cluster;
357         const char* bufp = buffer;
358         off_t lsize, loffset, remainder;
359
360         if (offset > node->size)
361                 if (exfat_truncate(ef, node, offset, true) != 0)
362                         return -1;
363         if (offset + size > node->size)
364                 if (exfat_truncate(ef, node, offset + size, false) != 0)
365                         return -1;
366         if (size == 0)
367                 return 0;
368
369         cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
370         if (CLUSTER_INVALID(cluster))
371         {
372                 exfat_error("invalid cluster 0x%x while writing", cluster);
373                 return -1;
374         }
375
376         loffset = offset % CLUSTER_SIZE(*ef->sb);
377         remainder = size;
378         while (remainder > 0)
379         {
380                 if (CLUSTER_INVALID(cluster))
381                 {
382                         exfat_error("invalid cluster 0x%x while writing", cluster);
383                         return -1;
384                 }
385                 lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
386                 exfat_pwrite(ef->dev, bufp, lsize, exfat_c2o(ef, cluster) + loffset);
387                 bufp += lsize;
388                 loffset = 0;
389                 remainder -= lsize;
390                 cluster = exfat_next_cluster(ef, node, cluster);
391         }
392         exfat_update_mtime(node);
393         return size - remainder;
394 }