Imported Upstream version 1.0.1
[sven/exfat-utils.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 #else
255         return lseek(dev->fd, offset, whence);
256 #endif
257 }
258
259 ssize_t exfat_read(struct exfat_dev* dev, void* buffer, size_t size)
260 {
261 #ifdef USE_UBLIO
262         ssize_t result = ublio_pread(dev->ufh, buffer, size, dev->pos);
263         if (result >= 0)
264                 dev->pos += size;
265         return result;
266 #else
267         return read(dev->fd, buffer, size);
268 #endif
269 }
270
271 ssize_t exfat_write(struct exfat_dev* dev, const void* buffer, size_t size)
272 {
273 #ifdef USE_UBLIO
274         ssize_t result = ublio_pwrite(dev->ufh, buffer, size, dev->pos);
275         if (result >= 0)
276                 dev->pos += size;
277         return result;
278 #else
279         return write(dev->fd, buffer, size);
280 #endif
281 }
282
283 void exfat_pread(struct exfat_dev* dev, void* buffer, size_t size,
284                 off_t offset)
285 {
286 #ifdef USE_UBLIO
287         if (ublio_pread(dev->ufh, buffer, size, offset) != size)
288 #else
289         if (pread(dev->fd, buffer, size, offset) != size)
290 #endif
291                 exfat_bug("failed to read %zu bytes from file at %"PRIu64, size,
292                                 (uint64_t) offset);
293 }
294
295 void exfat_pwrite(struct exfat_dev* dev, const void* buffer, size_t size,
296                 off_t offset)
297 {
298 #ifdef USE_UBLIO
299         if (ublio_pwrite(dev->ufh, buffer, size, offset) != size)
300 #else
301         if (pwrite(dev->fd, buffer, size, offset) != size)
302 #endif
303                 exfat_bug("failed to write %zu bytes to file at %"PRIu64, size,
304                                 (uint64_t) offset);
305 }
306
307 ssize_t exfat_generic_pread(const struct exfat* ef, struct exfat_node* node,
308                 void* buffer, size_t size, off_t offset)
309 {
310         cluster_t cluster;
311         char* bufp = buffer;
312         off_t lsize, loffset, remainder;
313
314         if (offset >= node->size)
315                 return 0;
316         if (size == 0)
317                 return 0;
318
319         cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
320         if (CLUSTER_INVALID(cluster))
321         {
322                 exfat_error("invalid cluster 0x%x while reading", cluster);
323                 return -1;
324         }
325
326         loffset = offset % CLUSTER_SIZE(*ef->sb);
327         remainder = MIN(size, node->size - offset);
328         while (remainder > 0)
329         {
330                 if (CLUSTER_INVALID(cluster))
331                 {
332                         exfat_error("invalid cluster 0x%x while reading", cluster);
333                         return -1;
334                 }
335                 lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
336                 exfat_pread(ef->dev, bufp, lsize, exfat_c2o(ef, cluster) + loffset);
337                 bufp += lsize;
338                 loffset = 0;
339                 remainder -= lsize;
340                 cluster = exfat_next_cluster(ef, node, cluster);
341         }
342         if (!ef->ro && !ef->noatime)
343                 exfat_update_atime(node);
344         return MIN(size, node->size - offset) - remainder;
345 }
346
347 ssize_t exfat_generic_pwrite(struct exfat* ef, struct exfat_node* node,
348                 const void* buffer, size_t size, off_t offset)
349 {
350         cluster_t cluster;
351         const char* bufp = buffer;
352         off_t lsize, loffset, remainder;
353
354         if (offset + size > node->size)
355                 if (exfat_truncate(ef, node, offset + size) != 0)
356                         return -1;
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 }