OSDN Git Service

052aa8db5bf83cf473e75cbc5e89874a66ecdb6f
[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 <sys/mount.h>
28 #include <fcntl.h>
29 #include <unistd.h>
30 #include <string.h>
31 #ifdef __APPLE__
32 #include <sys/disk.h>
33 #endif
34 #ifdef USE_UBLIO
35 #include <sys/uio.h>
36 #include <ublio.h>
37 #endif
38
39 struct exfat_dev
40 {
41         int fd;
42         enum exfat_mode mode;
43         off_t size; /* in bytes */
44 #ifdef USE_UBLIO
45         off_t pos;
46         ublio_filehandle_t ufh;
47 #endif
48 };
49
50 static int open_ro(const char* spec)
51 {
52         return open(spec, O_RDONLY);
53 }
54
55 static int open_rw(const char* spec)
56 {
57         int fd = open(spec, O_RDWR);
58 #ifdef __linux__
59         int ro = 0;
60
61         /*
62            This ioctl is needed because after "blockdev --setro" kernel still
63            allows to open the device in read-write mode but fails writes.
64         */
65         if (fd != -1 && ioctl(fd, BLKROGET, &ro) == 0 && ro)
66         {
67                 close(fd);
68                 return -1;
69         }
70 #endif
71         return fd;
72 }
73
74 struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)
75 {
76         struct exfat_dev* dev;
77         struct stat stbuf;
78 #ifdef USE_UBLIO
79         struct ublio_param up;
80 #endif
81
82         dev = malloc(sizeof(struct exfat_dev));
83         if (dev == NULL)
84         {
85                 exfat_error("failed to allocate memory for device structure");
86                 return NULL;
87         }
88
89         switch (mode)
90         {
91         case EXFAT_MODE_RO:
92                 dev->fd = open_ro(spec);
93                 if (dev->fd == -1)
94                 {
95                         free(dev);
96                         exfat_error("failed to open `%s' in read-only mode", spec);
97                         return NULL;
98                 }
99                 dev->mode = EXFAT_MODE_RO;
100                 break;
101         case EXFAT_MODE_RW:
102                 dev->fd = open_rw(spec);
103                 if (dev->fd == -1)
104                 {
105                         free(dev);
106                         exfat_error("failed to open `%s' in read-write mode", spec);
107                         return NULL;
108                 }
109                 dev->mode = EXFAT_MODE_RW;
110                 break;
111         case EXFAT_MODE_ANY:
112                 dev->fd = open_rw(spec);
113                 if (dev->fd != -1)
114                 {
115                         dev->mode = EXFAT_MODE_RW;
116                         break;
117                 }
118                 dev->fd = open_ro(spec);
119                 if (dev->fd != -1)
120                 {
121                         dev->mode = EXFAT_MODE_RO;
122                         exfat_warn("`%s' is write-protected, mounting read-only", spec);
123                         break;
124                 }
125                 free(dev);
126                 exfat_error("failed to open `%s'", spec);
127                 return NULL;
128         }
129
130         if (fstat(dev->fd, &stbuf) != 0)
131         {
132                 close(dev->fd);
133                 free(dev);
134                 exfat_error("failed to fstat `%s'", spec);
135                 return NULL;
136         }
137         if (!S_ISBLK(stbuf.st_mode) &&
138                 !S_ISCHR(stbuf.st_mode) &&
139                 !S_ISREG(stbuf.st_mode))
140         {
141                 close(dev->fd);
142                 free(dev);
143                 exfat_error("`%s' is neither a device, nor a regular file", spec);
144                 return NULL;
145         }
146
147 #ifdef __APPLE__
148         if (!S_ISREG(stbuf.st_mode))
149         {
150                 uint32_t block_size = 0;
151                 uint64_t blocks = 0;
152
153                 if (ioctl(dev->fd, DKIOCGETBLOCKSIZE, &block_size) != 0)
154                 {
155                         close(dev->fd);
156                         free(dev);
157                         exfat_error("failed to get block size");
158                         return NULL;
159                 }
160                 if (ioctl(dev->fd, DKIOCGETBLOCKCOUNT, &blocks) != 0)
161                 {
162                         close(dev->fd);
163                         free(dev);
164                         exfat_error("failed to get blocks count");
165                         return NULL;
166                 }
167                 dev->size = blocks * block_size;
168         }
169         else
170 #endif
171         {
172                 /* works for Linux, FreeBSD, Solaris */
173                 dev->size = exfat_seek(dev, 0, SEEK_END);
174                 if (dev->size <= 0)
175                 {
176                         close(dev->fd);
177                         free(dev);
178                         exfat_error("failed to get size of `%s'", spec);
179                         return NULL;
180                 }
181                 if (exfat_seek(dev, 0, SEEK_SET) == -1)
182                 {
183                         close(dev->fd);
184                         free(dev);
185                         exfat_error("failed to seek to the beginning of `%s'", spec);
186                         return NULL;
187                 }
188         }
189
190 #ifdef USE_UBLIO
191         memset(&up, 0, sizeof(struct ublio_param));
192         up.up_blocksize = 256 * 1024;
193         up.up_items = 64;
194         up.up_grace = 32;
195         up.up_priv = &dev->fd;
196
197         dev->pos = 0;
198         dev->ufh = ublio_open(&up);
199         if (dev->ufh == NULL)
200         {
201                 close(dev->fd);
202                 free(dev);
203                 exfat_error("failed to initialize ublio");
204                 return NULL;
205         }
206 #endif
207
208         return dev;
209 }
210
211 int exfat_close(struct exfat_dev* dev)
212 {
213 #ifdef USE_UBLIO
214         if (ublio_close(dev->ufh) != 0)
215                 exfat_error("failed to close ublio");
216 #endif
217         if (close(dev->fd) != 0)
218         {
219                 free(dev);
220                 exfat_error("failed to close device");
221                 return 1;
222         }
223         free(dev);
224         return 0;
225 }
226
227 int exfat_fsync(struct exfat_dev* dev)
228 {
229 #ifdef USE_UBLIO
230         if (ublio_fsync(dev->ufh) != 0)
231 #else
232         if (fsync(dev->fd) != 0)
233 #endif
234         {
235                 exfat_error("fsync failed");
236                 return 1;
237         }
238         return 0;
239 }
240
241 enum exfat_mode exfat_get_mode(const struct exfat_dev* dev)
242 {
243         return dev->mode;
244 }
245
246 off_t exfat_get_size(const struct exfat_dev* dev)
247 {
248         return dev->size;
249 }
250
251 off_t exfat_seek(struct exfat_dev* dev, off_t offset, int whence)
252 {
253 #ifdef USE_UBLIO
254         /* XXX SEEK_CUR will be handled incorrectly */
255         return dev->pos = lseek(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 #else
291         if (pread(dev->fd, buffer, size, offset) != size)
292 #endif
293                 exfat_bug("failed to read %zu bytes from file at %"PRIu64, size,
294                                 (uint64_t) offset);
295 }
296
297 void exfat_pwrite(struct exfat_dev* dev, const void* buffer, size_t size,
298                 off_t offset)
299 {
300 #ifdef USE_UBLIO
301         if (ublio_pwrite(dev->ufh, buffer, size, offset) != size)
302 #else
303         if (pwrite(dev->fd, buffer, size, offset) != size)
304 #endif
305                 exfat_bug("failed to write %zu bytes to file at %"PRIu64, size,
306                                 (uint64_t) offset);
307 }
308
309 ssize_t exfat_generic_pread(const struct exfat* ef, struct exfat_node* node,
310                 void* buffer, size_t size, off_t offset)
311 {
312         cluster_t cluster;
313         char* bufp = buffer;
314         off_t lsize, loffset, remainder;
315
316         if (offset >= node->size)
317                 return 0;
318         if (size == 0)
319                 return 0;
320
321         cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
322         if (CLUSTER_INVALID(cluster))
323         {
324                 exfat_error("invalid cluster 0x%x while reading", cluster);
325                 return -1;
326         }
327
328         loffset = offset % CLUSTER_SIZE(*ef->sb);
329         remainder = MIN(size, node->size - offset);
330         while (remainder > 0)
331         {
332                 if (CLUSTER_INVALID(cluster))
333                 {
334                         exfat_error("invalid cluster 0x%x while reading", cluster);
335                         return -1;
336                 }
337                 lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
338                 exfat_pread(ef->dev, bufp, lsize, exfat_c2o(ef, cluster) + loffset);
339                 bufp += lsize;
340                 loffset = 0;
341                 remainder -= lsize;
342                 cluster = exfat_next_cluster(ef, node, cluster);
343         }
344         if (!ef->ro && !ef->noatime)
345                 exfat_update_atime(node);
346         return MIN(size, node->size - offset) - remainder;
347 }
348
349 ssize_t exfat_generic_pwrite(struct exfat* ef, struct exfat_node* node,
350                 const void* buffer, size_t size, off_t offset)
351 {
352         cluster_t cluster;
353         const char* bufp = buffer;
354         off_t lsize, loffset, remainder;
355
356         if (offset > node->size)
357                 if (exfat_truncate(ef, node, offset, true) != 0)
358                         return -1;
359         if (offset + size > node->size)
360                 if (exfat_truncate(ef, node, offset + size, false) != 0)
361                         return -1;
362         if (size == 0)
363                 return 0;
364
365         cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
366         if (CLUSTER_INVALID(cluster))
367         {
368                 exfat_error("invalid cluster 0x%x while writing", cluster);
369                 return -1;
370         }
371
372         loffset = offset % CLUSTER_SIZE(*ef->sb);
373         remainder = size;
374         while (remainder > 0)
375         {
376                 if (CLUSTER_INVALID(cluster))
377                 {
378                         exfat_error("invalid cluster 0x%x while writing", cluster);
379                         return -1;
380                 }
381                 lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
382                 exfat_pwrite(ef->dev, bufp, lsize, exfat_c2o(ef, cluster) + loffset);
383                 bufp += lsize;
384                 loffset = 0;
385                 remainder -= lsize;
386                 cluster = exfat_next_cluster(ef, node, cluster);
387         }
388         exfat_update_mtime(node);
389         return size - remainder;
390 }