OSDN Git Service

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