diff options
Diffstat (limited to 'firmware/common/file.c')
-rw-r--r-- | firmware/common/file.c | 1637 |
1 files changed, 1010 insertions, 627 deletions
diff --git a/firmware/common/file.c b/firmware/common/file.c index 920eada84e..7d3b5092ae 100644 --- a/firmware/common/file.c +++ b/firmware/common/file.c | |||
@@ -8,6 +8,7 @@ | |||
8 | * $Id$ | 8 | * $Id$ |
9 | * | 9 | * |
10 | * Copyright (C) 2002 by Björn Stenberg | 10 | * Copyright (C) 2002 by Björn Stenberg |
11 | * Copyright (C) 2014 by Michael Sevakis | ||
11 | * | 12 | * |
12 | * This program is free software; you can redistribute it and/or | 13 | * This program is free software; you can redistribute it and/or |
13 | * modify it under the terms of the GNU General Public License | 14 | * modify it under the terms of the GNU General Public License |
@@ -18,819 +19,1201 @@ | |||
18 | * KIND, either express or implied. | 19 | * KIND, either express or implied. |
19 | * | 20 | * |
20 | ****************************************************************************/ | 21 | ****************************************************************************/ |
22 | #define RB_FILESYSTEM_OS | ||
23 | #include "config.h" | ||
24 | #include "system.h" | ||
21 | #include <string.h> | 25 | #include <string.h> |
22 | #include <errno.h> | 26 | #include <errno.h> |
23 | #include <stdbool.h> | ||
24 | #include "file.h" | ||
25 | #include "fat.h" | ||
26 | #include "dir_uncached.h" | ||
27 | #include "debug.h" | 27 | #include "debug.h" |
28 | #include "dircache.h" | 28 | #include "file.h" |
29 | #include "filefuncs.h" | 29 | #include "fileobj_mgr.h" |
30 | #include "system.h" | 30 | #include "disk_cache.h" |
31 | #include "dircache_redirect.h" | ||
32 | #include "string-extra.h" | ||
33 | |||
34 | /** | ||
35 | * These functions provide a roughly POSIX-compatible file I/O API. | ||
36 | */ | ||
37 | |||
38 | /* structure used for open file descriptors */ | ||
39 | static struct filestr_desc | ||
40 | { | ||
41 | struct filestr_base stream; /* basic stream info (first!) */ | ||
42 | file_size_t offset; /* current offset for stream */ | ||
43 | file_size_t *sizep; /* shortcut to file size in fileobj */ | ||
44 | } open_streams[MAX_OPEN_FILES]; | ||
31 | 45 | ||
32 | /* | 46 | /* check and return a struct filestr_desc* from a file descriptor number */ |
33 | These functions provide a roughly POSIX-compatible file IO API. | 47 | static struct filestr_desc * get_filestr(int fildes) |
48 | { | ||
49 | struct filestr_desc *file = &open_streams[fildes]; | ||
34 | 50 | ||
35 | Since the fat32 driver only manages sectors, we maintain a one-sector | 51 | if ((unsigned int)fildes >= MAX_OPEN_FILES) |
36 | cache for each open file. This way we can provide byte access without | 52 | file = NULL; |
37 | having to re-read the sector each time. | 53 | else if (file->stream.flags & FDO_BUSY) |
38 | The penalty is the RAM used for the cache and slightly more complex code. | 54 | return file; |
39 | */ | ||
40 | 55 | ||
41 | struct filedesc { | 56 | DEBUGF("fildes %d: bad file number\n", fildes); |
42 | unsigned char cache[SECTOR_SIZE] CACHEALIGN_ATTR; | 57 | errno = (file && file->stream.flags == FV_NONEXIST) ? ENXIO : EBADF; |
43 | int cacheoffset; /* invariant: 0 <= cacheoffset <= SECTOR_SIZE */ | 58 | return NULL; |
44 | long fileoffset; | 59 | } |
45 | long size; | ||
46 | int attr; | ||
47 | struct fat_file fatfile; | ||
48 | bool busy; | ||
49 | bool write; | ||
50 | bool dirty; | ||
51 | bool trunc; | ||
52 | } CACHEALIGN_ATTR; | ||
53 | 60 | ||
54 | static struct filedesc openfiles[MAX_OPEN_FILES] CACHEALIGN_ATTR; | 61 | #define GET_FILESTR(type, fildes) \ |
62 | ({ \ | ||
63 | file_internal_lock_##type(); \ | ||
64 | struct filestr_desc * _file = get_filestr(fildes); \ | ||
65 | if (_file) \ | ||
66 | FILESTR_LOCK(type, &_file->stream); \ | ||
67 | else \ | ||
68 | file_internal_unlock_##type(); \ | ||
69 | _file; \ | ||
70 | }) | ||
71 | |||
72 | /* release the lock on the filestr_desc* */ | ||
73 | #define RELEASE_FILESTR(type, file) \ | ||
74 | ({ \ | ||
75 | FILESTR_UNLOCK(type, &(file)->stream); \ | ||
76 | file_internal_unlock_##type(); \ | ||
77 | }) | ||
78 | |||
79 | /* find a free file descriptor */ | ||
80 | static int alloc_filestr(struct filestr_desc **filep) | ||
81 | { | ||
82 | for (int fildes = 0; fildes < MAX_OPEN_FILES; fildes++) | ||
83 | { | ||
84 | struct filestr_desc *file = &open_streams[fildes]; | ||
85 | if (!file->stream.flags) | ||
86 | { | ||
87 | *filep = file; | ||
88 | return fildes; | ||
89 | } | ||
90 | } | ||
55 | 91 | ||
56 | static int flush_cache(int fd); | 92 | DEBUGF("Too many files open\n"); |
93 | return -1; | ||
94 | } | ||
57 | 95 | ||
58 | int file_creat(const char *pathname) | 96 | /* return the file size in sectors */ |
97 | static inline unsigned long filesize_sectors(file_size_t size) | ||
59 | { | 98 | { |
60 | return open(pathname, O_WRONLY|O_CREAT|O_TRUNC, 0666); | 99 | /* overflow proof whereas "(x + y - 1) / y" is not */ |
100 | unsigned long numsectors = size / SECTOR_SIZE; | ||
101 | |||
102 | if (size % SECTOR_SIZE) | ||
103 | numsectors++; | ||
104 | |||
105 | return numsectors; | ||
61 | } | 106 | } |
62 | 107 | ||
63 | static int open_internal(const char* pathname, int flags, bool use_cache) | 108 | /* flush a dirty cache buffer */ |
109 | static int flush_cache(struct filestr_desc *file) | ||
64 | { | 110 | { |
65 | DIR_UNCACHED* dir; | ||
66 | struct dirent_uncached* entry; | ||
67 | int fd; | ||
68 | int pathnamesize = strlen(pathname) + 1; | ||
69 | char pathnamecopy[pathnamesize]; | ||
70 | char* name; | ||
71 | struct filedesc* file = NULL; | ||
72 | int rc; | 111 | int rc; |
73 | #ifndef HAVE_DIRCACHE | 112 | struct filestr_cache *cachep = file->stream.cachep; |
74 | (void)use_cache; | ||
75 | #endif | ||
76 | 113 | ||
77 | LDEBUGF("open(\"%s\",%d)\n",pathname,flags); | 114 | DEBUGF("Flushing dirty sector cache (%lu)\n", cachep->sector); |
78 | 115 | ||
79 | if ( pathname[0] != '/' ) { | 116 | if (fat_query_sectornum(&file->stream.fatstr) != cachep->sector) |
80 | DEBUGF("'%s' is not an absolute path.\n",pathname); | 117 | { |
81 | DEBUGF("Only absolute pathnames supported at the moment\n"); | 118 | /* get on the correct sector */ |
82 | errno = EINVAL; | 119 | rc = fat_seek(&file->stream.fatstr, cachep->sector); |
83 | return -1; | 120 | if (rc < 0) |
121 | FILE_ERROR(EIO, rc * 10 - 1); | ||
84 | } | 122 | } |
85 | 123 | ||
86 | /* find a free file descriptor */ | 124 | rc = fat_readwrite(&file->stream.fatstr, 1, cachep->buffer, true); |
87 | for ( fd=0; fd<MAX_OPEN_FILES; fd++ ) | 125 | if (rc < 0) |
88 | if ( !openfiles[fd].busy ) | 126 | { |
89 | break; | 127 | if (rc == FAT_RC_ENOSPC) |
90 | 128 | FILE_ERROR(ENOSPC, RC); | |
91 | if ( fd == MAX_OPEN_FILES ) { | 129 | else |
92 | DEBUGF("Too many files open\n"); | 130 | FILE_ERROR(EIO, rc * 10 - 2); |
93 | errno = EMFILE; | ||
94 | return -2; | ||
95 | } | 131 | } |
96 | 132 | ||
97 | file = &openfiles[fd]; | 133 | cachep->flags = 0; |
98 | memset(file, 0, sizeof(struct filedesc)); | 134 | return 1; |
135 | file_error: | ||
136 | DEBUGF("Failed flushing cache: %d\n", rc); | ||
137 | return rc; | ||
138 | } | ||
139 | |||
140 | static void discard_cache(struct filestr_desc *file) | ||
141 | { | ||
142 | struct filestr_cache *const cachep = file->stream.cachep; | ||
143 | cachep->flags = 0; | ||
144 | } | ||
99 | 145 | ||
100 | if (flags & (O_RDWR | O_WRONLY)) { | 146 | /* set the file pointer */ |
101 | file->write = true; | 147 | static off_t lseek_internal(struct filestr_desc *file, off_t offset, |
148 | int whence) | ||
149 | { | ||
150 | off_t rc; | ||
151 | file_size_t pos; | ||
102 | 152 | ||
103 | if (flags & O_TRUNC) | 153 | file_size_t size = MIN(*file->sizep, FILE_SIZE_MAX); |
104 | file->trunc = true; | ||
105 | } | ||
106 | file->busy = true; | ||
107 | 154 | ||
108 | #ifdef HAVE_DIRCACHE | 155 | switch (whence) |
109 | if (dircache_is_enabled() && !file->write && use_cache) | ||
110 | { | 156 | { |
111 | # ifdef HAVE_MULTIVOLUME | 157 | case SEEK_SET: |
112 | int volume = strip_volume(pathname, pathnamecopy); | 158 | if (offset < 0 || (file_size_t)offset > size) |
113 | # endif | 159 | FILE_ERROR(EINVAL, -1); |
114 | 160 | ||
115 | int ce = dircache_get_entry_id(pathname); | 161 | pos = offset; |
116 | if (ce < 0) | 162 | break; |
117 | { | ||
118 | errno = ENOENT; | ||
119 | file->busy = false; | ||
120 | return -7; | ||
121 | } | ||
122 | 163 | ||
123 | long startcluster = _dircache_get_entry_startcluster(ce); | 164 | case SEEK_CUR: |
124 | fat_open(IF_MV(volume,) | 165 | if ((offset < 0 && (file_size_t)-offset > file->offset) || |
125 | startcluster, | 166 | (offset > 0 && (file_size_t)offset > size - file->offset)) |
126 | &(file->fatfile), | 167 | FILE_ERROR(EINVAL, -1); |
127 | NULL); | ||
128 | struct dirinfo *info = _dircache_get_entry_dirinfo(ce); | ||
129 | file->size = info->size; | ||
130 | file->attr = info->attribute; | ||
131 | file->cacheoffset = -1; | ||
132 | file->fileoffset = 0; | ||
133 | 168 | ||
134 | return fd; | 169 | pos = file->offset + offset; |
135 | } | 170 | break; |
136 | #endif | ||
137 | 171 | ||
138 | strlcpy(pathnamecopy, pathname, pathnamesize); | 172 | case SEEK_END: |
173 | if (offset > 0 || (file_size_t)-offset > size) | ||
174 | FILE_ERROR(EINVAL, -1); | ||
139 | 175 | ||
140 | /* locate filename */ | 176 | pos = size + offset; |
141 | name=strrchr(pathnamecopy+1,'/'); | 177 | break; |
142 | if ( name ) { | 178 | |
143 | *name = 0; | 179 | default: |
144 | dir = opendir_uncached(pathnamecopy); | 180 | FILE_ERROR(EINVAL, -1); |
145 | *name = '/'; | ||
146 | name++; | ||
147 | } | ||
148 | else { | ||
149 | dir = opendir_uncached("/"); | ||
150 | name = pathnamecopy+1; | ||
151 | } | ||
152 | if (!dir) { | ||
153 | DEBUGF("Failed opening dir\n"); | ||
154 | errno = EIO; | ||
155 | file->busy = false; | ||
156 | return -4; | ||
157 | } | ||
158 | |||
159 | if(name[0] == 0) { | ||
160 | DEBUGF("Empty file name\n"); | ||
161 | errno = EINVAL; | ||
162 | file->busy = false; | ||
163 | closedir_uncached(dir); | ||
164 | return -5; | ||
165 | } | ||
166 | |||
167 | /* scan dir for name */ | ||
168 | while ((entry = readdir_uncached(dir))) { | ||
169 | if ( !strcasecmp(name, entry->d_name) ) { | ||
170 | fat_open(IF_MV(dir->fatdir.file.volume,) | ||
171 | entry->startcluster, | ||
172 | &(file->fatfile), | ||
173 | &(dir->fatdir)); | ||
174 | file->size = file->trunc ? 0 : entry->info.size; | ||
175 | file->attr = entry->info.attribute; | ||
176 | break; | ||
177 | } | ||
178 | } | 181 | } |
179 | 182 | ||
180 | if ( !entry ) { | 183 | file->offset = pos; |
181 | LDEBUGF("Didn't find file %s\n",name); | 184 | |
182 | if ( file->write && (flags & O_CREAT) ) { | 185 | return pos; |
183 | rc = fat_create_file(name, | 186 | file_error: |
184 | &(file->fatfile), | 187 | return rc; |
185 | &(dir->fatdir)); | 188 | } |
186 | if (rc < 0) { | 189 | |
187 | DEBUGF("Couldn't create %s in %s\n",name,pathnamecopy); | 190 | /* callback for each file stream to make sure all data is in sync with new |
188 | errno = EIO; | 191 | size */ |
189 | file->busy = false; | 192 | void ftruncate_internal_callback(struct filestr_base *stream, |
190 | closedir_uncached(dir); | 193 | struct filestr_base *s) |
191 | return rc * 10 - 6; | 194 | { |
195 | struct filestr_desc *file = (struct filestr_desc *)s; | ||
196 | file_size_t size = *file->sizep; | ||
197 | |||
198 | /* caches with data beyond new extents are invalid */ | ||
199 | unsigned long sector = file->stream.cachep->sector; | ||
200 | if (sector != INVALID_SECNUM && sector >= filesize_sectors(size)) | ||
201 | filestr_discard_cache(&file->stream); | ||
202 | |||
203 | /* keep all positions within bounds */ | ||
204 | if (file->offset > size) | ||
205 | file->offset = size; | ||
206 | |||
207 | (void)stream; | ||
208 | } | ||
209 | |||
210 | /* truncate the file to the specified length */ | ||
211 | static int ftruncate_internal(struct filestr_desc *file, file_size_t size, | ||
212 | bool write_now) | ||
213 | { | ||
214 | int rc = 0, rc2 = 1; | ||
215 | |||
216 | file_size_t cursize = *file->sizep; | ||
217 | file_size_t truncsize = MIN(size, cursize); | ||
218 | |||
219 | if (write_now) | ||
220 | { | ||
221 | unsigned long sector = filesize_sectors(truncsize); | ||
222 | struct filestr_cache *const cachep = file->stream.cachep; | ||
223 | |||
224 | if (cachep->flags == (FSC_NEW|FSC_DIRTY) && | ||
225 | cachep->sector + 1 == sector) | ||
226 | { | ||
227 | /* sector created but may have never been added to the cluster | ||
228 | chain; flush it now or the subsequent may fail */ | ||
229 | rc2 = flush_cache(file); | ||
230 | if (rc2 == FAT_RC_ENOSPC) | ||
231 | { | ||
232 | /* no space left on device; further truncation needed */ | ||
233 | discard_cache(file); | ||
234 | truncsize = ALIGN_DOWN(truncsize - 1, SECTOR_SIZE); | ||
235 | sector--; | ||
236 | rc = rc2; | ||
192 | } | 237 | } |
193 | #ifdef HAVE_DIRCACHE | 238 | else if (rc2 < 0) |
194 | dircache_add_file(pathname, file->fatfile.firstcluster); | 239 | FILE_ERROR(ERRNO, rc2 * 10 - 1); |
195 | #endif | ||
196 | file->size = 0; | ||
197 | file->attr = 0; | ||
198 | } | ||
199 | else { | ||
200 | DEBUGF("Couldn't find %s in %s\n",name,pathnamecopy); | ||
201 | errno = ENOENT; | ||
202 | file->busy = false; | ||
203 | closedir_uncached(dir); | ||
204 | return -7; | ||
205 | } | ||
206 | } else { | ||
207 | if(file->write && (file->attr & FAT_ATTR_DIRECTORY)) { | ||
208 | errno = EISDIR; | ||
209 | file->busy = false; | ||
210 | closedir_uncached(dir); | ||
211 | return -8; | ||
212 | } | 240 | } |
213 | } | ||
214 | closedir_uncached(dir); | ||
215 | 241 | ||
216 | file->cacheoffset = -1; | 242 | rc2 = fat_seek(&file->stream.fatstr, sector); |
217 | file->fileoffset = 0; | 243 | if (rc2 < 0) |
244 | FILE_ERROR(EIO, rc2 * 10 - 2); | ||
218 | 245 | ||
219 | if (file->write && (flags & O_APPEND)) { | 246 | rc2 = fat_truncate(&file->stream.fatstr); |
220 | rc = lseek(fd,0,SEEK_END); | 247 | if (rc2 < 0) |
221 | if (rc < 0 ) | 248 | FILE_ERROR(EIO, rc2 * 10 - 3); |
222 | return rc * 10 - 9; | ||
223 | } | 249 | } |
250 | /* else just change the cached file size */ | ||
224 | 251 | ||
225 | #ifdef HAVE_DIRCACHE | 252 | if (truncsize < cursize) |
226 | if (file->write) | 253 | { |
227 | dircache_bind(fd, pathname); | 254 | *file->sizep = truncsize; |
228 | #endif | 255 | fileop_ontruncate_internal(&file->stream); |
256 | } | ||
229 | 257 | ||
230 | return fd; | 258 | /* if truncation was partially successful, it effectively destroyed |
231 | } | 259 | everything after the truncation point; still, indicate failure |
260 | after adjusting size */ | ||
261 | if (rc2 == 0) | ||
262 | FILE_ERROR(EIO, -4); | ||
263 | else if (rc2 < 0) | ||
264 | FILE_ERROR(ERRNO, rc2); | ||
232 | 265 | ||
233 | int file_open(const char* pathname, int flags) | 266 | file_error: |
234 | { | 267 | return rc; |
235 | /* By default, use the dircache if available. */ | ||
236 | return open_internal(pathname, flags, true); | ||
237 | } | 268 | } |
238 | 269 | ||
239 | int close(int fd) | 270 | /* flush back all outstanding writes to the file */ |
271 | static int fsync_internal(struct filestr_desc *file) | ||
240 | { | 272 | { |
241 | struct filedesc* file = &openfiles[fd]; | 273 | /* call only when holding WRITER lock (updates directory entries) */ |
242 | int rc = 0; | 274 | int rc = 0; |
243 | 275 | ||
244 | LDEBUGF("close(%d)\n", fd); | 276 | file_size_t size = *file->sizep; |
277 | unsigned int foflags = fileobj_get_flags(&file->stream); | ||
278 | |||
279 | /* flush sector cache? */ | ||
280 | struct filestr_cache *const cachep = file->stream.cachep; | ||
281 | if (cachep->flags & FSC_DIRTY) | ||
282 | { | ||
283 | int rc2 = flush_cache(file); | ||
284 | if (rc2 == FAT_RC_ENOSPC && (cachep->flags & FSC_NEW)) | ||
285 | { | ||
286 | /* no space left on device so this must be dropped */ | ||
287 | discard_cache(file); | ||
288 | size = ALIGN_DOWN(size - 1, SECTOR_SIZE); | ||
289 | foflags |= FO_TRUNC; | ||
290 | rc = rc2; | ||
291 | } | ||
292 | else if (rc2 < 0) | ||
293 | FILE_ERROR(ERRNO, rc2 * 10 - 1); | ||
294 | } | ||
295 | |||
296 | /* truncate? */ | ||
297 | if (foflags & FO_TRUNC) | ||
298 | { | ||
299 | int rc2 = ftruncate_internal(file, size, rc == 0); | ||
300 | if (rc2 < 0) | ||
301 | FILE_ERROR(ERRNO, rc2 * 10 - 2); | ||
245 | 302 | ||
246 | if (fd < 0 || fd > MAX_OPEN_FILES-1) { | 303 | /* never needs to be done this way again since any data beyond the |
247 | errno = EINVAL; | 304 | cached size is now gone */ |
248 | return -1; | 305 | fileobj_change_flags(&file->stream, 0, FO_TRUNC); |
249 | } | 306 | } |
250 | if (!file->busy) { | 307 | |
251 | errno = EBADF; | 308 | file_error:; |
252 | return -2; | 309 | /* tie up all loose ends (try to close the file even if failing) */ |
310 | int rc2 = fat_closewrite(&file->stream.fatstr, size, | ||
311 | get_dir_fatent_dircache()); | ||
312 | if (rc2 >= 0) | ||
313 | fileop_onsync_internal(&file->stream); /* dir_fatent is implicit arg */ | ||
314 | |||
315 | if (rc2 < 0 && rc >= 0) | ||
316 | { | ||
317 | errno = EIO; | ||
318 | rc = rc2 * 10 - 3; | ||
253 | } | 319 | } |
254 | if (file->write) { | 320 | |
255 | rc = fsync(fd); | 321 | return rc; |
322 | } | ||
323 | |||
324 | /* finish with the file and free resources */ | ||
325 | static int close_internal(struct filestr_desc *file) | ||
326 | { | ||
327 | /* call only when holding WRITER lock (updates directory entries) */ | ||
328 | int rc; | ||
329 | |||
330 | if ((file->stream.flags & FD_WRITE) && | ||
331 | !(fileobj_get_flags(&file->stream) & FO_REMOVED)) | ||
332 | { | ||
333 | rc = fsync_internal(file); | ||
256 | if (rc < 0) | 334 | if (rc < 0) |
257 | return rc * 10 - 3; | 335 | FILE_ERROR(ERRNO, rc * 10 - 1); |
258 | #ifdef HAVE_DIRCACHE | ||
259 | dircache_update_filesize(fd, file->size, file->fatfile.firstcluster); | ||
260 | dircache_update_filetime(fd); | ||
261 | #endif | ||
262 | } | 336 | } |
263 | 337 | ||
264 | file->busy = false; | 338 | rc = 0; |
265 | return 0; | 339 | file_error:; |
340 | int rc2 = close_stream_internal(&file->stream); | ||
341 | if (rc2 < 0 && rc >= 0) | ||
342 | rc = rc2 * 10 - 2; | ||
343 | return rc; | ||
266 | } | 344 | } |
267 | 345 | ||
268 | int fsync(int fd) | 346 | /* actually do the open gruntwork */ |
347 | static int open_internal_inner2(const char *path, | ||
348 | struct filestr_desc *file, | ||
349 | unsigned int callflags) | ||
269 | { | 350 | { |
270 | struct filedesc* file = &openfiles[fd]; | 351 | int rc; |
271 | int rc = 0; | ||
272 | |||
273 | LDEBUGF("fsync(%d)\n", fd); | ||
274 | 352 | ||
275 | if (fd < 0 || fd > MAX_OPEN_FILES-1) { | 353 | struct path_component_info compinfo; |
276 | errno = EINVAL; | 354 | rc = open_stream_internal(path, callflags, &file->stream, &compinfo); |
277 | return -1; | 355 | if (rc < 0) |
278 | } | 356 | { |
279 | if (!file->busy) { | 357 | DEBUGF("Open failed: %d\n", rc); |
280 | errno = EBADF; | 358 | FILE_ERROR_RETURN(ERRNO, rc * 10 - 1); |
281 | return -2; | ||
282 | } | 359 | } |
283 | if (file->write) { | 360 | |
284 | /* flush sector cache */ | 361 | bool created = false; |
285 | if ( file->dirty ) { | 362 | |
286 | rc = flush_cache(fd); | 363 | if (rc > 0) |
287 | if (rc < 0) | 364 | { |
288 | { | 365 | if (callflags & FF_EXCL) |
289 | /* when failing, try to close the file anyway */ | 366 | { |
290 | fat_closewrite(&(file->fatfile), file->size, file->attr); | 367 | DEBUGF("File exists\n"); |
291 | return rc * 10 - 3; | 368 | FILE_ERROR(EEXIST, -2); |
292 | } | ||
293 | } | 369 | } |
294 | 370 | ||
295 | /* truncate? */ | 371 | if (compinfo.attr & ATTR_DIRECTORY) |
296 | if (file->trunc) { | 372 | { |
297 | rc = ftruncate(fd, file->size); | 373 | if ((callflags & FD_WRITE) || !(callflags & FF_ANYTYPE)) |
298 | if (rc < 0) | ||
299 | { | 374 | { |
300 | /* when failing, try to close the file anyway */ | 375 | DEBUGF("File is a directory\n"); |
301 | fat_closewrite(&(file->fatfile), file->size, file->attr); | 376 | FILE_ERROR(EISDIR, -3); |
302 | return rc * 10 - 4; | ||
303 | } | 377 | } |
378 | |||
379 | compinfo.filesize = MAX_DIRECTORY_SIZE; /* allow file ops */ | ||
380 | } | ||
381 | } | ||
382 | else if (callflags & FF_CREAT) | ||
383 | { | ||
384 | if (compinfo.attr & ATTR_DIRECTORY) | ||
385 | { | ||
386 | DEBUGF("File is a directory\n"); | ||
387 | FILE_ERROR(EISDIR, -5); | ||
304 | } | 388 | } |
305 | 389 | ||
306 | /* tie up all loose ends */ | 390 | /* not found; try to create it */ |
307 | rc = fat_closewrite(&(file->fatfile), file->size, file->attr); | 391 | |
392 | callflags &= ~FO_TRUNC; | ||
393 | rc = create_stream_internal(&compinfo.parentinfo, compinfo.name, | ||
394 | compinfo.length, ATTR_NEW_FILE, callflags, | ||
395 | &file->stream); | ||
308 | if (rc < 0) | 396 | if (rc < 0) |
309 | return rc * 10 - 5; | 397 | FILE_ERROR(ERRNO, rc * 10 - 6); |
310 | } | ||
311 | return 0; | ||
312 | } | ||
313 | 398 | ||
314 | int remove(const char* name) | 399 | created = true; |
315 | { | ||
316 | int rc; | ||
317 | struct filedesc* file; | ||
318 | /* Can't use dircache now, because we need to access the fat structures. */ | ||
319 | int fd = open_internal(name, O_WRONLY, false); | ||
320 | if ( fd < 0 ) | ||
321 | return fd * 10 - 1; | ||
322 | |||
323 | file = &openfiles[fd]; | ||
324 | #ifdef HAVE_DIRCACHE | ||
325 | dircache_remove(name); | ||
326 | #endif | ||
327 | rc = fat_remove(&(file->fatfile)); | ||
328 | if ( rc < 0 ) { | ||
329 | DEBUGF("Failed removing file: %d\n", rc); | ||
330 | errno = EIO; | ||
331 | return rc * 10 - 3; | ||
332 | } | 400 | } |
401 | else | ||
402 | { | ||
403 | DEBUGF("File not found\n"); | ||
404 | FILE_ERROR(ENOENT, -7); | ||
405 | } | ||
406 | |||
407 | fat_rewind(&file->stream.fatstr); | ||
408 | file->sizep = fileobj_get_sizep(&file->stream); | ||
409 | file->offset = 0; | ||
410 | |||
411 | if (!created) | ||
412 | { | ||
413 | /* size from storage applies to first stream only otherwise it's | ||
414 | already up to date */ | ||
415 | const bool first = fileobj_get_flags(&file->stream) & FO_SINGLE; | ||
416 | if (first) | ||
417 | *file->sizep = compinfo.filesize; | ||
333 | 418 | ||
334 | file->size = 0; | 419 | if (callflags & FO_TRUNC) |
420 | { | ||
421 | /* if the file is kind of "big" then free some space now */ | ||
422 | rc = ftruncate_internal(file, 0, *file->sizep >= O_TRUNC_THRESH); | ||
423 | if (rc < 0) | ||
424 | { | ||
425 | DEBUGF("O_TRUNC failed: %d\n", rc); | ||
426 | FILE_ERROR(ERRNO, rc * 10 - 4); | ||
427 | } | ||
428 | } | ||
429 | } | ||
335 | 430 | ||
336 | rc = close(fd); | 431 | rc = 0; |
337 | if (rc<0) | 432 | file_error: |
338 | return rc * 10 - 4; | 433 | if (rc < 0) |
434 | close_stream_internal(&file->stream); | ||
339 | 435 | ||
340 | return 0; | 436 | return rc; |
341 | } | 437 | } |
342 | 438 | ||
343 | int rename(const char* path, const char* newpath) | 439 | /* allocate a file descriptor, if needed, assemble stream flags and open |
440 | a new stream */ | ||
441 | static int open_internal_inner1(const char *path, int oflag, | ||
442 | unsigned int callflags) | ||
344 | { | 443 | { |
345 | int rc, fd; | 444 | DEBUGF("%s(path=\"%s\",oflag=%X,callflags=%X)\n", __func__, |
346 | DIR_UNCACHED* dir; | 445 | path, oflag, callflags); |
347 | char* nameptr; | ||
348 | char* dirptr; | ||
349 | struct filedesc* file; | ||
350 | char newpath2[MAX_PATH]; | ||
351 | 446 | ||
352 | /* verify new path does not already exist */ | 447 | int rc; |
353 | /* If it is a directory, errno == EISDIR if the name exists */ | ||
354 | fd = open(newpath, O_RDONLY); | ||
355 | if ( fd >= 0 || errno == EISDIR) { | ||
356 | close(fd); | ||
357 | errno = EBUSY; | ||
358 | return -1; | ||
359 | } | ||
360 | close(fd); | ||
361 | 448 | ||
362 | fd = open_internal(path, O_RDONLY, false); | 449 | struct filestr_desc *file; |
363 | if ( fd < 0 ) { | 450 | int fildes = alloc_filestr(&file); |
364 | errno = EIO; | 451 | if (fildes < 0) |
365 | return fd * 10 - 2; | 452 | FILE_ERROR(EMFILE, -1); |
366 | } | ||
367 | 453 | ||
368 | /* extract new file name */ | 454 | callflags &= ~FDO_MASK; |
369 | nameptr = strrchr(newpath,'/'); | ||
370 | if (nameptr) | ||
371 | nameptr++; | ||
372 | else { | ||
373 | close(fd); | ||
374 | return - 3; | ||
375 | } | ||
376 | 455 | ||
377 | /* Extract new path */ | 456 | if (oflag & O_ACCMODE) |
378 | strcpy(newpath2, newpath); | 457 | { |
458 | callflags |= FD_WRITE; | ||
379 | 459 | ||
380 | dirptr = strrchr(newpath2,'/'); | 460 | if ((oflag & O_ACCMODE) == O_WRONLY) |
381 | if(dirptr) | 461 | callflags |= FD_WRONLY; |
382 | *dirptr = 0; | ||
383 | else { | ||
384 | close(fd); | ||
385 | return - 4; | ||
386 | } | ||
387 | 462 | ||
388 | dirptr = newpath2; | 463 | if (oflag & O_APPEND) |
464 | callflags |= FD_APPEND; | ||
389 | 465 | ||
390 | if(strlen(dirptr) == 0) { | 466 | if (oflag & O_TRUNC) |
391 | dirptr = "/"; | 467 | callflags |= FO_TRUNC; |
392 | } | 468 | } |
393 | 469 | else if (oflag & O_TRUNC) | |
394 | dir = opendir_uncached(dirptr); | 470 | { |
395 | if(!dir) { | 471 | /* O_TRUNC requires write mode */ |
396 | close(fd); | 472 | DEBUGF("No write mode but have O_TRUNC\n"); |
397 | return - 5; | 473 | FILE_ERROR(EINVAL, -2); |
398 | } | 474 | } |
399 | 475 | ||
400 | file = &openfiles[fd]; | 476 | /* O_CREAT and O_APPEND are fine without write mode |
477 | * for the former, an empty file is created but no data may be written | ||
478 | * for the latter, no append will be allowed anyway */ | ||
479 | if (oflag & O_CREAT) | ||
480 | { | ||
481 | callflags |= FF_CREAT; | ||
401 | 482 | ||
402 | rc = fat_rename(&file->fatfile, &dir->fatdir, nameptr, | 483 | if (oflag & O_EXCL) |
403 | file->size, file->attr); | 484 | callflags |= FF_EXCL; |
404 | #ifdef HAVE_MULTIVOLUME | ||
405 | if ( rc == -1) { | ||
406 | close(fd); | ||
407 | closedir_uncached(dir); | ||
408 | DEBUGF("Failed renaming file across volumnes: %d\n", rc); | ||
409 | errno = EXDEV; | ||
410 | return -6; | ||
411 | } | ||
412 | #endif | ||
413 | if ( rc < 0 ) { | ||
414 | close(fd); | ||
415 | closedir_uncached(dir); | ||
416 | DEBUGF("Failed renaming file: %d\n", rc); | ||
417 | errno = EIO; | ||
418 | return rc * 10 - 7; | ||
419 | } | 485 | } |
420 | 486 | ||
421 | #ifdef HAVE_DIRCACHE | 487 | rc = open_internal_inner2(path, file, callflags); |
422 | dircache_rename(path, newpath); | 488 | if (rc < 0) |
423 | #endif | 489 | FILE_ERROR(ERRNO, rc * 10 - 3); |
424 | 490 | ||
425 | rc = close(fd); | 491 | return fildes; |
426 | if (rc<0) { | ||
427 | closedir_uncached(dir); | ||
428 | errno = EIO; | ||
429 | return rc * 10 - 8; | ||
430 | } | ||
431 | 492 | ||
432 | rc = closedir_uncached(dir); | 493 | file_error: |
433 | if (rc<0) { | 494 | return rc; |
434 | errno = EIO; | 495 | } |
435 | return rc * 10 - 9; | ||
436 | } | ||
437 | 496 | ||
438 | return 0; | 497 | static int open_internal_locked(const char *path, int oflag, |
498 | unsigned int callflags) | ||
499 | { | ||
500 | file_internal_lock_WRITER(); | ||
501 | int rc = open_internal_inner1(path, oflag, callflags); | ||
502 | file_internal_unlock_WRITER(); | ||
503 | return rc; | ||
439 | } | 504 | } |
440 | 505 | ||
441 | int ftruncate(int fd, off_t size) | 506 | /* fill a cache buffer with a new sector */ |
507 | static int readwrite_fill_cache(struct filestr_desc *file, unsigned long sector, | ||
508 | unsigned long filesectors, bool write) | ||
442 | { | 509 | { |
443 | int rc, sector; | 510 | /* sector != cachep->sector should have been checked by now */ |
444 | struct filedesc* file = &openfiles[fd]; | ||
445 | 511 | ||
446 | sector = size / SECTOR_SIZE; | 512 | int rc; |
447 | if (size % SECTOR_SIZE) | 513 | struct filestr_cache *cachep = filestr_get_cache(&file->stream); |
448 | sector++; | ||
449 | 514 | ||
450 | rc = fat_seek(&(file->fatfile), sector); | 515 | if (cachep->flags & FSC_DIRTY) |
451 | if (rc < 0) { | 516 | { |
452 | errno = EIO; | 517 | rc = flush_cache(file); |
453 | return rc * 10 - 1; | 518 | if (rc < 0) |
519 | FILE_ERROR(ERRNO, rc * 10 - 1); | ||
454 | } | 520 | } |
455 | 521 | ||
456 | rc = fat_truncate(&(file->fatfile)); | 522 | if (fat_query_sectornum(&file->stream.fatstr) != sector) |
457 | if (rc < 0) { | 523 | { |
458 | errno = EIO; | 524 | /* get on the correct sector */ |
459 | return rc * 10 - 2; | 525 | rc = fat_seek(&file->stream.fatstr, sector); |
526 | if (rc < 0) | ||
527 | FILE_ERROR(EIO, rc * 10 - 2); | ||
460 | } | 528 | } |
461 | 529 | ||
462 | file->size = size; | 530 | if (!write || sector < filesectors) |
463 | #ifdef HAVE_DIRCACHE | 531 | { |
464 | dircache_update_filesize(fd, size, file->fatfile.firstcluster); | 532 | /* only reading or this sector would have been flushed if the cache |
465 | #endif | 533 | was previously needed for a different sector */ |
534 | rc = fat_readwrite(&file->stream.fatstr, 1, cachep->buffer, false); | ||
535 | if (rc < 0) | ||
536 | FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO, rc * 10 - 3); | ||
537 | } | ||
538 | else | ||
539 | { | ||
540 | /* create a fresh, shiny, new sector with that new sector smell */ | ||
541 | cachep->flags = FSC_NEW; | ||
542 | } | ||
466 | 543 | ||
467 | return 0; | 544 | cachep->sector = sector; |
545 | return 1; | ||
546 | file_error: | ||
547 | DEBUGF("Failed caching sector: %d\n", rc); | ||
548 | return rc; | ||
468 | } | 549 | } |
469 | 550 | ||
470 | static int flush_cache(int fd) | 551 | /* read or write to part or all of the cache buffer */ |
552 | static inline void readwrite_cache(struct filestr_cache *cachep, void *buf, | ||
553 | unsigned long secoffset, size_t nbyte, | ||
554 | bool write) | ||
471 | { | 555 | { |
472 | int rc; | 556 | void *dst, *cbufp = cachep->buffer + secoffset; |
473 | struct filedesc* file = &openfiles[fd]; | ||
474 | long sector = file->fileoffset / SECTOR_SIZE; | ||
475 | |||
476 | DEBUGF("Flushing dirty sector cache\n"); | ||
477 | 557 | ||
478 | /* make sure we are on correct sector */ | 558 | if (write) |
479 | rc = fat_seek(&(file->fatfile), sector); | 559 | { |
480 | if ( rc < 0 ) | 560 | dst = cbufp; |
481 | return rc * 10 - 3; | 561 | cachep->flags |= FSC_DIRTY; |
482 | 562 | } | |
483 | rc = fat_readwrite(&(file->fatfile), 1, file->cache, true ); | 563 | else |
564 | { | ||
565 | dst = buf; | ||
566 | buf = cbufp; | ||
567 | } | ||
484 | 568 | ||
485 | if ( rc < 0 ) { | 569 | memcpy(dst, buf, nbyte); |
486 | if(file->fatfile.eof) | 570 | } |
487 | errno = ENOSPC; | ||
488 | 571 | ||
489 | return rc * 10 - 2; | 572 | /* read or write a partial sector using the file's cache */ |
573 | static inline ssize_t readwrite_partial(struct filestr_desc *file, | ||
574 | struct filestr_cache *cachep, | ||
575 | unsigned long sector, | ||
576 | unsigned long secoffset, | ||
577 | void *buf, | ||
578 | size_t nbyte, | ||
579 | unsigned long filesectors, | ||
580 | unsigned int flags) | ||
581 | { | ||
582 | if (sector != cachep->sector) | ||
583 | { | ||
584 | /* wrong sector in buffer */ | ||
585 | int rc = readwrite_fill_cache(file, sector, filesectors, flags); | ||
586 | if (rc <= 0) | ||
587 | return rc; | ||
490 | } | 588 | } |
491 | 589 | ||
492 | file->dirty = false; | 590 | readwrite_cache(cachep, buf, secoffset, nbyte, flags); |
493 | 591 | return nbyte; | |
494 | return 0; | ||
495 | } | 592 | } |
496 | 593 | ||
497 | static int readwrite(int fd, void* buf, long count, bool write) | 594 | /* read from or write to the file; back end to read() and write() */ |
595 | static ssize_t readwrite(struct filestr_desc *file, void *buf, size_t nbyte, | ||
596 | bool write) | ||
498 | { | 597 | { |
499 | long sectors; | 598 | DEBUGF("readwrite(%p,%lx,%ld,%s)\n", |
500 | long nread=0; | 599 | file, (long)buf, nbyte, write ? "write" : "read"); |
501 | struct filedesc* file; | ||
502 | int rc; | ||
503 | #ifdef STORAGE_NEEDS_ALIGN | ||
504 | long i; | ||
505 | int rc2; | ||
506 | #endif | ||
507 | 600 | ||
508 | if (fd < 0 || fd > MAX_OPEN_FILES-1) { | 601 | const file_size_t size = *file->sizep; |
509 | errno = EINVAL; | 602 | file_size_t filerem; |
510 | return -1; | ||
511 | } | ||
512 | 603 | ||
513 | file = &openfiles[fd]; | 604 | if (write) |
605 | { | ||
606 | /* if opened in append mode, move pointer to end */ | ||
607 | if (file->stream.flags & FD_APPEND) | ||
608 | file->offset = MIN(size, FILE_SIZE_MAX); | ||
514 | 609 | ||
515 | if ( !file->busy ) { | 610 | filerem = FILE_SIZE_MAX - file->offset; |
516 | errno = EBADF; | 611 | } |
517 | return -1; | 612 | else |
613 | { | ||
614 | /* limit to maximum possible offset (EOF or FILE_SIZE_MAX) */ | ||
615 | filerem = MIN(size, FILE_SIZE_MAX) - file->offset; | ||
518 | } | 616 | } |
519 | 617 | ||
520 | if(file->attr & FAT_ATTR_DIRECTORY) { | 618 | if (nbyte > filerem) |
521 | errno = EISDIR; | 619 | { |
522 | return -1; | 620 | nbyte = filerem; |
621 | if (nbyte > 0) | ||
622 | {} | ||
623 | else if (write) | ||
624 | FILE_ERROR_RETURN(EFBIG, -1); /* would get too large */ | ||
625 | else if (file->offset >= FILE_SIZE_MAX) | ||
626 | FILE_ERROR_RETURN(EOVERFLOW, -2); /* can't read here */ | ||
523 | } | 627 | } |
524 | 628 | ||
525 | LDEBUGF( "readwrite(%d,%lx,%ld,%s)\n", | 629 | if (nbyte == 0) |
526 | fd,(long)buf,count,write?"write":"read"); | 630 | return 0; |
527 | 631 | ||
528 | /* attempt to read past EOF? */ | 632 | int rc = 0; |
529 | if (!write && count > file->size - file->fileoffset) | 633 | |
530 | count = file->size - file->fileoffset; | 634 | struct filestr_cache * const cachep = file->stream.cachep; |
635 | void * const bufstart = buf; | ||
636 | |||
637 | const unsigned long filesectors = filesize_sectors(size); | ||
638 | unsigned long sector = file->offset / SECTOR_SIZE; | ||
639 | unsigned long sectoroffs = file->offset % SECTOR_SIZE; | ||
531 | 640 | ||
532 | /* any head bytes? */ | 641 | /* any head bytes? */ |
533 | if ( file->cacheoffset != -1 ) { | 642 | if (sectoroffs) |
534 | int offs = file->cacheoffset; | 643 | { |
535 | int headbytes = MIN(count, SECTOR_SIZE - offs); | 644 | size_t headbytes = MIN(nbyte, SECTOR_SIZE - sectoroffs); |
645 | rc = readwrite_partial(file, cachep, sector, sectoroffs, buf, headbytes, | ||
646 | filesectors, write); | ||
647 | if (rc <= 0) | ||
648 | { | ||
649 | if (rc < 0) | ||
650 | FILE_ERROR(ERRNO, rc * 10 - 3); | ||
536 | 651 | ||
537 | if (write) { | 652 | nbyte = 0; /* eof, skip the rest */ |
538 | memcpy( file->cache + offs, buf, headbytes ); | ||
539 | file->dirty = true; | ||
540 | } | 653 | } |
541 | else { | 654 | else |
542 | memcpy( buf, file->cache + offs, headbytes ); | 655 | { |
656 | buf += rc; | ||
657 | nbyte -= rc; | ||
658 | sector++; /* if nbyte goes to 0, the rest is skipped anyway */ | ||
543 | } | 659 | } |
660 | } | ||
544 | 661 | ||
545 | if (offs + headbytes == SECTOR_SIZE) { | 662 | /* read/write whole sectors right into/from the supplied buffer */ |
546 | if (file->dirty) { | 663 | unsigned long sectorcount = nbyte / SECTOR_SIZE; |
547 | rc = flush_cache(fd); | ||
548 | if ( rc < 0 ) { | ||
549 | errno = EIO; | ||
550 | return rc * 10 - 2; | ||
551 | } | ||
552 | } | ||
553 | file->cacheoffset = -1; | ||
554 | } | ||
555 | else { | ||
556 | file->cacheoffset += headbytes; | ||
557 | } | ||
558 | 664 | ||
559 | nread = headbytes; | 665 | while (sectorcount) |
560 | count -= headbytes; | 666 | { |
561 | } | 667 | unsigned long runlen = sectorcount; |
562 | 668 | ||
563 | /* If the buffer has been modified, either it has been flushed already | 669 | /* if a cached sector is inside the transfer range, split the transfer |
564 | * (if (offs+headbytes == SECTOR_SIZE)...) or does not need to be (no | 670 | into two parts and use the cache for that sector to keep it coherent |
565 | * more data to follow in this call). Do NOT flush here. */ | 671 | without writeback */ |
672 | if (UNLIKELY(cachep->sector >= sector && | ||
673 | cachep->sector < sector + sectorcount)) | ||
674 | { | ||
675 | runlen = cachep->sector - sector; | ||
676 | } | ||
566 | 677 | ||
567 | /* read/write whole sectors right into/from the supplied buffer */ | 678 | if (runlen) |
568 | sectors = count / SECTOR_SIZE; | 679 | { |
569 | rc = 0; | 680 | if (fat_query_sectornum(&file->stream.fatstr) != sector) |
570 | if ( sectors ) { | ||
571 | #ifdef STORAGE_NEEDS_ALIGN | ||
572 | if (((uint32_t)buf + nread) & (CACHEALIGN_SIZE - 1)) | ||
573 | for (i = 0; i < sectors; i++) | ||
574 | { | 681 | { |
575 | if (write) memcpy(file->cache, buf+nread+i*SECTOR_SIZE, SECTOR_SIZE); | 682 | /* get on the correct sector */ |
576 | rc2 = fat_readwrite(&(file->fatfile), 1, file->cache, write ); | 683 | rc = 0; |
577 | if (rc2 < 0) | 684 | |
685 | /* If the dirty bit isn't set, we're somehow beyond the file | ||
686 | size and you can't explain _that_ */ | ||
687 | if (sector >= filesectors && cachep->flags == (FSC_NEW|FSC_DIRTY)) | ||
578 | { | 688 | { |
579 | rc = rc2; | 689 | rc = flush_cache(file); |
580 | break; | 690 | if (rc < 0) |
691 | FILE_ERROR(ERRNO, rc * 10 - 4); | ||
692 | |||
693 | if (cachep->sector + 1 == sector) | ||
694 | rc = 1; /* if now ok, don't seek */ | ||
695 | } | ||
696 | |||
697 | if (rc == 0) | ||
698 | { | ||
699 | rc = fat_seek(&file->stream.fatstr, sector); | ||
700 | if (rc < 0) | ||
701 | FILE_ERROR(EIO, rc * 10 - 5); | ||
581 | } | 702 | } |
582 | else rc += rc2; | ||
583 | if (!write) memcpy(buf+nread+i*SECTOR_SIZE, file->cache, SECTOR_SIZE); | ||
584 | } | ||
585 | else | ||
586 | #endif | ||
587 | rc = fat_readwrite(&(file->fatfile), sectors, (unsigned char*)buf+nread, write ); | ||
588 | if ( rc < 0 ) { | ||
589 | DEBUGF("Failed read/writing %ld sectors\n",sectors); | ||
590 | errno = EIO; | ||
591 | if(write && file->fatfile.eof) { | ||
592 | DEBUGF("No space left on device\n"); | ||
593 | errno = ENOSPC; | ||
594 | } else { | ||
595 | file->fileoffset += nread; | ||
596 | } | 703 | } |
597 | file->cacheoffset = -1; | 704 | |
598 | /* adjust file size to length written */ | 705 | rc = fat_readwrite(&file->stream.fatstr, runlen, buf, write); |
599 | if ( write && file->fileoffset > file->size ) | 706 | if (rc < 0) |
600 | { | 707 | { |
601 | file->size = file->fileoffset; | 708 | DEBUGF("I/O error %sing %ld sectors\n", sectors, |
602 | #ifdef HAVE_DIRCACHE | 709 | write ? "writ" : "read"); |
603 | dircache_update_filesize(fd, file->size, file->fatfile.firstcluster); | 710 | FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO, |
604 | #endif | 711 | rc * 10 - 6); |
605 | } | 712 | } |
606 | return nread ? nread : rc * 10 - 4; | 713 | else |
607 | } | 714 | { |
608 | else { | 715 | buf += rc * SECTOR_SIZE; |
609 | if ( rc > 0 ) { | 716 | nbyte -= rc * SECTOR_SIZE; |
610 | nread += rc * SECTOR_SIZE; | 717 | sector += rc; |
611 | count -= sectors * SECTOR_SIZE; | 718 | sectorcount -= rc; |
612 | 719 | ||
613 | /* if eof, skip tail bytes */ | 720 | /* if eof, skip tail bytes */ |
614 | if ( rc < sectors ) | 721 | if ((unsigned long)rc < runlen) |
615 | count = 0; | 722 | nbyte = 0; |
616 | } | 723 | |
617 | else { | 724 | if (!nbyte) |
618 | /* eof */ | 725 | break; |
619 | count=0; | ||
620 | } | 726 | } |
727 | } | ||
621 | 728 | ||
622 | file->cacheoffset = -1; | 729 | if (UNLIKELY(sectorcount && sector == cachep->sector)) |
730 | { | ||
731 | /* do this one sector with the cache */ | ||
732 | readwrite_cache(cachep, buf, 0, SECTOR_SIZE, write); | ||
733 | buf += SECTOR_SIZE; | ||
734 | nbyte -= SECTOR_SIZE; | ||
735 | sector++; | ||
736 | sectorcount--; | ||
623 | } | 737 | } |
624 | } | 738 | } |
625 | 739 | ||
626 | /* any tail bytes? */ | 740 | /* any tail bytes? */ |
627 | if ( count ) { | 741 | if (nbyte) |
628 | if (write) { | 742 | { |
629 | if ( file->fileoffset + nread < file->size ) { | 743 | /* tail bytes always start at sector offset 0 */ |
630 | /* sector is only partially filled. copy-back from disk */ | 744 | rc = readwrite_partial(file, cachep, sector, 0, buf, nbyte, |
631 | LDEBUGF("Copy-back tail cache\n"); | 745 | filesectors, write); |
632 | rc = fat_readwrite(&(file->fatfile), 1, file->cache, false ); | 746 | if (rc < 0) |
633 | if ( rc < 0 ) { | 747 | FILE_ERROR(ERRNO, rc * 10 - 7); |
634 | DEBUGF("Failed writing\n"); | ||
635 | errno = EIO; | ||
636 | file->fileoffset += nread; | ||
637 | file->cacheoffset = -1; | ||
638 | /* adjust file size to length written */ | ||
639 | if ( file->fileoffset > file->size ) | ||
640 | { | ||
641 | file->size = file->fileoffset; | ||
642 | #ifdef HAVE_DIRCACHE | ||
643 | dircache_update_filesize(fd, file->size, file->fatfile.firstcluster); | ||
644 | #endif | ||
645 | } | ||
646 | return nread ? nread : rc * 10 - 5; | ||
647 | } | ||
648 | /* seek back one sector to put file position right */ | ||
649 | rc = fat_seek(&(file->fatfile), | ||
650 | (file->fileoffset + nread) / | ||
651 | SECTOR_SIZE); | ||
652 | if ( rc < 0 ) { | ||
653 | DEBUGF("fat_seek() failed\n"); | ||
654 | errno = EIO; | ||
655 | file->fileoffset += nread; | ||
656 | file->cacheoffset = -1; | ||
657 | /* adjust file size to length written */ | ||
658 | if ( file->fileoffset > file->size ) | ||
659 | { | ||
660 | file->size = file->fileoffset; | ||
661 | #ifdef HAVE_DIRCACHE | ||
662 | dircache_update_filesize(fd, file->size, file->fatfile.firstcluster); | ||
663 | #endif | ||
664 | } | ||
665 | return nread ? nread : rc * 10 - 6; | ||
666 | } | ||
667 | } | ||
668 | memcpy( file->cache, (unsigned char*)buf + nread, count ); | ||
669 | file->dirty = true; | ||
670 | } | ||
671 | else { | ||
672 | rc = fat_readwrite(&(file->fatfile), 1, file->cache,false); | ||
673 | if (rc < 1 ) { | ||
674 | DEBUGF("Failed caching sector\n"); | ||
675 | errno = EIO; | ||
676 | file->fileoffset += nread; | ||
677 | file->cacheoffset = -1; | ||
678 | return nread ? nread : rc * 10 - 7; | ||
679 | } | ||
680 | memcpy( (unsigned char*)buf + nread, file->cache, count ); | ||
681 | } | ||
682 | 748 | ||
683 | nread += count; | 749 | buf += rc; |
684 | file->cacheoffset = count; | ||
685 | } | 750 | } |
686 | 751 | ||
687 | file->fileoffset += nread; | 752 | file_error:; |
688 | LDEBUGF("fileoffset: %ld\n", file->fileoffset); | 753 | #ifdef DEBUG |
754 | if (errno == ENOSPC) | ||
755 | DEBUGF("No space left on device\n"); | ||
756 | #endif | ||
689 | 757 | ||
690 | /* adjust file size to length written */ | 758 | size_t done = buf - bufstart; |
691 | if ( write && file->fileoffset > file->size ) | 759 | if (done) |
692 | { | 760 | { |
693 | file->size = file->fileoffset; | 761 | /* error or not, update the file offset and size if anything was |
694 | #ifdef HAVE_DIRCACHE | 762 | transferred */ |
695 | dircache_update_filesize(fd, file->size, file->fatfile.firstcluster); | 763 | file->offset += done; |
696 | #endif | 764 | DEBUGF("file offset: %ld\n", file->offset); |
765 | |||
766 | /* adjust file size to length written */ | ||
767 | if (write && file->offset > size) | ||
768 | *file->sizep = file->offset; | ||
769 | |||
770 | if (rc > 0) | ||
771 | return done; | ||
697 | } | 772 | } |
698 | 773 | ||
699 | return nread; | 774 | return rc; |
700 | } | 775 | } |
701 | 776 | ||
702 | ssize_t write(int fd, const void* buf, size_t count) | 777 | |
778 | /** Internal interface **/ | ||
779 | |||
780 | /* open a file without codepage conversion during the directory search; | ||
781 | required to avoid any reentrancy when opening codepages and when scanning | ||
782 | directories internally, which could infinitely recurse and would corrupt | ||
783 | the static data */ | ||
784 | int open_noiso_internal(const char *path, int oflag) | ||
703 | { | 785 | { |
704 | if (!openfiles[fd].write) { | 786 | return open_internal_locked(path, oflag, FF_ANYTYPE | FF_NOISO); |
705 | errno = EACCES; | ||
706 | return -1; | ||
707 | } | ||
708 | return readwrite(fd, (void *)buf, count, true); | ||
709 | } | 787 | } |
710 | 788 | ||
711 | ssize_t read(int fd, void* buf, size_t count) | 789 | |
790 | /** POSIX **/ | ||
791 | |||
792 | /* open a file */ | ||
793 | int open(const char *path, int oflag) | ||
712 | { | 794 | { |
713 | return readwrite(fd, buf, count, false); | 795 | DEBUGF("open(path=\"%s\",oflag=%X)\n", path, (unsigned)oflag); |
796 | return open_internal_locked(path, oflag, FF_ANYTYPE); | ||
714 | } | 797 | } |
715 | 798 | ||
799 | /* create a new file or rewrite an existing one */ | ||
800 | int creat(const char *path) | ||
801 | { | ||
802 | DEBUGF("creat(path=\"%s\")\n", path); | ||
803 | return open_internal_locked(path, O_WRONLY|O_CREAT|O_TRUNC, FF_ANYTYPE); | ||
804 | } | ||
716 | 805 | ||
717 | off_t lseek(int fd, off_t offset, int whence) | 806 | /* close a file descriptor */ |
807 | int close(int fildes) | ||
718 | { | 808 | { |
719 | off_t pos; | 809 | DEBUGF("close(fd=%d)\n", fildes); |
720 | long newsector; | 810 | |
721 | long oldsector; | ||
722 | int sectoroffset; | ||
723 | int rc; | 811 | int rc; |
724 | struct filedesc* file = &openfiles[fd]; | ||
725 | 812 | ||
726 | LDEBUGF("lseek(%d,%ld,%d)\n",fd,offset,whence); | 813 | file_internal_lock_WRITER(); |
727 | 814 | ||
728 | if (fd < 0 || fd > MAX_OPEN_FILES-1) { | 815 | /* needs to work even if marked "nonexistant" */ |
729 | errno = EINVAL; | 816 | struct filestr_desc *file = &open_streams[fildes]; |
730 | return -1; | 817 | if ((unsigned int)fildes >= MAX_OPEN_FILES || !file->stream.flags) |
731 | } | 818 | { |
732 | if ( !file->busy ) { | 819 | DEBUGF("filedes %d not open\n", fildes); |
733 | errno = EBADF; | 820 | FILE_ERROR(EBADF, -2); |
734 | return -1; | ||
735 | } | 821 | } |
736 | 822 | ||
737 | switch ( whence ) { | 823 | rc = close_internal(file); |
738 | case SEEK_SET: | 824 | if (rc < 0) |
739 | pos = offset; | 825 | FILE_ERROR(ERRNO, rc * 10 - 3); |
740 | break; | ||
741 | 826 | ||
742 | case SEEK_CUR: | 827 | file_error: |
743 | pos = file->fileoffset + offset; | 828 | file_internal_unlock_WRITER(); |
744 | break; | 829 | return rc; |
830 | } | ||
831 | |||
832 | /* truncate a file to a specified length */ | ||
833 | int ftruncate(int fildes, off_t length) | ||
834 | { | ||
835 | DEBUGF("ftruncate(fd=%d,len=%ld)\n", fildes, (long)length); | ||
745 | 836 | ||
746 | case SEEK_END: | 837 | struct filestr_desc * const file = GET_FILESTR(READER, fildes); |
747 | pos = file->size + offset; | 838 | if (!file) |
748 | break; | 839 | FILE_ERROR_RETURN(ERRNO, -1); |
749 | 840 | ||
750 | default: | 841 | int rc; |
751 | errno = EINVAL; | 842 | |
752 | return -2; | 843 | if (!(file->stream.flags & FD_WRITE)) |
844 | { | ||
845 | DEBUGF("Descriptor is read-only mode\n"); | ||
846 | FILE_ERROR(EBADF, -2); | ||
753 | } | 847 | } |
754 | if ((pos < 0) || (pos > file->size)) { | 848 | |
755 | errno = EINVAL; | 849 | if (length < 0) |
756 | return -3; | 850 | { |
851 | DEBUGF("Length %ld is invalid\n", (long)length); | ||
852 | FILE_ERROR(EINVAL, -3); | ||
757 | } | 853 | } |
758 | 854 | ||
759 | /* new sector? */ | 855 | rc = ftruncate_internal(file, length, true); |
760 | newsector = pos / SECTOR_SIZE; | 856 | if (rc < 0) |
761 | oldsector = file->fileoffset / SECTOR_SIZE; | 857 | FILE_ERROR(ERRNO, rc * 10 - 4); |
762 | sectoroffset = pos % SECTOR_SIZE; | ||
763 | 858 | ||
764 | if ( (newsector != oldsector) || | 859 | file_error: |
765 | ((file->cacheoffset==-1) && sectoroffset) ) { | 860 | RELEASE_FILESTR(READER, file); |
861 | return rc; | ||
862 | } | ||
766 | 863 | ||
767 | if ( newsector != oldsector ) { | 864 | /* synchronize changes to a file */ |
768 | if (file->dirty) { | 865 | int fsync(int fildes) |
769 | rc = flush_cache(fd); | 866 | { |
770 | if (rc < 0) | 867 | DEBUGF("fsync(fd=%d)\n", fildes); |
771 | return rc * 10 - 5; | ||
772 | } | ||
773 | 868 | ||
774 | rc = fat_seek(&(file->fatfile), newsector); | 869 | struct filestr_desc * const file = GET_FILESTR(WRITER, fildes); |
775 | if ( rc < 0 ) { | 870 | if (!file) |
776 | errno = EIO; | 871 | FILE_ERROR_RETURN(ERRNO, -1); |
777 | return rc * 10 - 4; | 872 | |
778 | } | 873 | int rc; |
779 | } | 874 | |
780 | if ( sectoroffset ) { | 875 | if (!(file->stream.flags & FD_WRITE)) |
781 | rc = fat_readwrite(&(file->fatfile), 1, file->cache ,false); | 876 | { |
782 | if ( rc < 0 ) { | 877 | DEBUGF("Descriptor is read-only mode\n", fd); |
783 | errno = EIO; | 878 | FILE_ERROR(EINVAL, -2); |
784 | return rc * 10 - 6; | ||
785 | } | ||
786 | file->cacheoffset = sectoroffset; | ||
787 | } | ||
788 | else | ||
789 | file->cacheoffset = -1; | ||
790 | } | 879 | } |
791 | else | ||
792 | if ( file->cacheoffset != -1 ) | ||
793 | file->cacheoffset = sectoroffset; | ||
794 | 880 | ||
795 | file->fileoffset = pos; | 881 | rc = fsync_internal(file); |
882 | if (rc < 0) | ||
883 | FILE_ERROR(ERRNO, rc * 10 - 3); | ||
796 | 884 | ||
797 | return pos; | 885 | file_error: |
886 | RELEASE_FILESTR(WRITER, file); | ||
887 | return rc; | ||
798 | } | 888 | } |
799 | 889 | ||
800 | off_t filesize(int fd) | 890 | /* move the read/write file offset */ |
891 | off_t lseek(int fildes, off_t offset, int whence) | ||
801 | { | 892 | { |
802 | struct filedesc* file = &openfiles[fd]; | 893 | DEBUGF("lseek(fd=%d,ofs=%ld,wh=%d)\n", fildes, (long)offset, whence); |
803 | 894 | ||
804 | if (fd < 0 || fd > MAX_OPEN_FILES-1) { | 895 | struct filestr_desc * const file = GET_FILESTR(READER, fildes); |
805 | errno = EINVAL; | 896 | if (!file) |
806 | return -1; | 897 | FILE_ERROR_RETURN(ERRNO, -1); |
898 | |||
899 | off_t rc = lseek_internal(file, offset, whence); | ||
900 | if (rc < 0) | ||
901 | FILE_ERROR(ERRNO, rc * 10 - 2); | ||
902 | |||
903 | file_error: | ||
904 | RELEASE_FILESTR(READER, file); | ||
905 | return rc; | ||
906 | } | ||
907 | |||
908 | /* read from a file */ | ||
909 | ssize_t read(int fildes, void *buf, size_t nbyte) | ||
910 | { | ||
911 | struct filestr_desc * const file = GET_FILESTR(READER, fildes); | ||
912 | if (!file) | ||
913 | FILE_ERROR_RETURN(ERRNO, -1); | ||
914 | |||
915 | ssize_t rc; | ||
916 | |||
917 | if (file->stream.flags & FD_WRONLY) | ||
918 | { | ||
919 | DEBUGF("read(fd=%d,buf=%p,nb=%lu) - " | ||
920 | "descriptor is write-only mode\n", fildes, buf, nbyte); | ||
921 | FILE_ERROR(EBADF, -2); | ||
807 | } | 922 | } |
808 | if ( !file->busy ) { | 923 | |
809 | errno = EBADF; | 924 | rc = readwrite(file, buf, nbyte, false); |
810 | return -1; | 925 | if (rc < 0) |
926 | FILE_ERROR(ERRNO, rc * 10 - 3); | ||
927 | |||
928 | file_error: | ||
929 | RELEASE_FILESTR(READER, file); | ||
930 | return rc; | ||
931 | } | ||
932 | |||
933 | /* write on a file */ | ||
934 | ssize_t write(int fildes, const void *buf, size_t nbyte) | ||
935 | { | ||
936 | struct filestr_desc * const file = GET_FILESTR(READER, fildes); | ||
937 | if (!file) | ||
938 | FILE_ERROR_RETURN(ERRNO, -1); | ||
939 | |||
940 | ssize_t rc; | ||
941 | |||
942 | if (!(file->stream.flags & FD_WRITE)) | ||
943 | { | ||
944 | DEBUGF("write(fd=%d,buf=%p,nb=%lu) - " | ||
945 | "descriptor is read-only mode\n", fildes, buf, nbyte); | ||
946 | FILE_ERROR(EBADF, -2); | ||
811 | } | 947 | } |
812 | 948 | ||
813 | return file->size; | 949 | rc = readwrite(file, (void *)buf, nbyte, true); |
950 | if (rc < 0) | ||
951 | FILE_ERROR(ERRNO, rc * 10 - 3); | ||
952 | |||
953 | file_error: | ||
954 | RELEASE_FILESTR(READER, file); | ||
955 | return rc; | ||
814 | } | 956 | } |
815 | 957 | ||
958 | /* remove a file */ | ||
959 | int remove(const char *path) | ||
960 | { | ||
961 | DEBUGF("remove(path=\"%s\")\n", path); | ||
962 | |||
963 | file_internal_lock_WRITER(); | ||
964 | int rc = remove_stream_internal(path, NULL, FF_FILE); | ||
965 | file_internal_unlock_WRITER(); | ||
966 | return rc; | ||
967 | } | ||
816 | 968 | ||
817 | /* release all file handles on a given volume "by force", to avoid leaks */ | 969 | /* rename a file */ |
818 | int release_files(int volume) | 970 | int rename(const char *old, const char *new) |
819 | { | 971 | { |
820 | struct filedesc* pfile = openfiles; | 972 | DEBUGF("rename(old=\"%s\",new=\"%s\")\n", old, new); |
821 | int fd; | 973 | |
822 | int closed = 0; | 974 | int rc, open1rc = -1, open2rc = -1; |
823 | for ( fd=0; fd<MAX_OPEN_FILES; fd++, pfile++) | 975 | struct filestr_base oldstr, newstr; |
976 | struct path_component_info oldinfo, newinfo; | ||
977 | |||
978 | file_internal_lock_WRITER(); | ||
979 | |||
980 | /* open 'old'; it must exist */ | ||
981 | open1rc = open_stream_internal(old, FF_ANYTYPE, &oldstr, &oldinfo); | ||
982 | if (open1rc <= 0) | ||
824 | { | 983 | { |
984 | DEBUGF("Failed opening old: %d\n", rc); | ||
985 | if (open1rc == 0) | ||
986 | FILE_ERROR(ENOENT, -1); | ||
987 | else | ||
988 | FILE_ERROR(ERRNO, open1rc * 10 - 1); | ||
989 | } | ||
990 | |||
991 | /* if 'old' is a directory then 'new' is also required to be one if 'new' | ||
992 | is to be overwritten */ | ||
993 | const bool are_dirs = oldinfo.attr & ATTR_DIRECTORY; | ||
994 | |||
995 | /* open new (may or may not exist) */ | ||
996 | unsigned int callflags = FF_FILE; | ||
997 | if (are_dirs) | ||
998 | { | ||
999 | /* if 'old' is found while parsing the new directory components then | ||
1000 | 'new' contains path prefix that names 'old'; if new and old are in | ||
1001 | the same directory, this tests positive but that is checked later */ | ||
1002 | callflags = FF_DIR | FF_CHECKPREFIX; | ||
1003 | newinfo.prefixp = oldstr.infop; | ||
1004 | } | ||
1005 | |||
1006 | open2rc = open_stream_internal(new, callflags, &newstr, &newinfo); | ||
1007 | if (open2rc < 0) | ||
1008 | { | ||
1009 | DEBUGF("Failed opening new file: %d\n", rc); | ||
1010 | FILE_ERROR(ERRNO, open2rc * 10 - 2); | ||
1011 | } | ||
1012 | |||
825 | #ifdef HAVE_MULTIVOLUME | 1013 | #ifdef HAVE_MULTIVOLUME |
826 | if (pfile->fatfile.volume == volume) | 1014 | if (oldinfo.parentinfo.volume != newinfo.parentinfo.volume) |
827 | #else | 1015 | { |
828 | (void)volume; | 1016 | DEBUGF("Cross-device link\n"); |
829 | #endif | 1017 | FILE_ERROR(EXDEV, -3); |
1018 | } | ||
1019 | #endif /* HAVE_MULTIVOLUME */ | ||
1020 | |||
1021 | /* if the parent is changing then this is a move, not a simple rename */ | ||
1022 | const bool is_move = !fat_file_is_same(&oldinfo.parentinfo.fatfile, | ||
1023 | &newinfo.parentinfo.fatfile); | ||
1024 | /* prefix found and moving? */ | ||
1025 | if (is_move && (newinfo.attr & ATTR_PREFIX)) | ||
1026 | { | ||
1027 | DEBUGF("New contains prefix that names old\n"); | ||
1028 | FILE_ERROR(EINVAL, -4); | ||
1029 | } | ||
1030 | |||
1031 | const char * const oldname = strmemdupa(oldinfo.name, oldinfo.length); | ||
1032 | const char * const newname = strmemdupa(newinfo.name, newinfo.length); | ||
1033 | bool is_overwrite = false; | ||
1034 | |||
1035 | if (open2rc > 0) | ||
1036 | { | ||
1037 | /* new name exists in parent; check if 'old' is overwriting 'new'; | ||
1038 | if it's the very same file, then it's just a rename */ | ||
1039 | is_overwrite = oldstr.bindp != newstr.bindp; | ||
1040 | |||
1041 | if (is_overwrite) | ||
1042 | { | ||
1043 | if (are_dirs) | ||
1044 | { | ||
1045 | /* the directory to be overwritten must be empty */ | ||
1046 | rc = test_dir_empty_internal(&newstr); | ||
1047 | if (rc < 0) | ||
1048 | FILE_ERROR(ERRNO, rc * 10 - 5); | ||
1049 | } | ||
1050 | } | ||
1051 | else if (!strcmp(newname, oldname)) /* case-only is ok */ | ||
1052 | { | ||
1053 | DEBUGF("No name change (success)\n"); | ||
1054 | rc = 0; | ||
1055 | FILE_ERROR(ERRNO, RC); | ||
1056 | } | ||
1057 | } | ||
1058 | else if (!are_dirs && (newinfo.attr & ATTR_DIRECTORY)) | ||
1059 | { | ||
1060 | /* even if new doesn't exist, canonical path type must match | ||
1061 | (ie. a directory path such as "/foo/bar/" when old names a file) */ | ||
1062 | DEBUGF("New path is a directory\n"); | ||
1063 | FILE_ERROR(EISDIR, -6); | ||
1064 | } | ||
1065 | |||
1066 | /* first, create the new entry so that there's never a time that the | ||
1067 | victim's data has no reference in the directory tree, that is, until | ||
1068 | everything else first succeeds */ | ||
1069 | struct file_base_info old_fileinfo = *oldstr.infop; | ||
1070 | rc = fat_rename(&newinfo.parentinfo.fatfile, &oldstr.infop->fatfile, | ||
1071 | newname); | ||
1072 | if (rc < 0) | ||
1073 | { | ||
1074 | DEBUGF("I/O error renaming file: %d\n", rc); | ||
1075 | FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO, rc * 10 - 7); | ||
1076 | } | ||
1077 | |||
1078 | if (is_overwrite) | ||
1079 | { | ||
1080 | /* 'new' would have been assigned its own directory entry and | ||
1081 | succeeded so at this point it is treated like a remove() call | ||
1082 | on the victim which preserves data until the last reference is | ||
1083 | closed */ | ||
1084 | rc = remove_stream_internal(NULL, &newstr, callflags); | ||
1085 | if (rc < 0) | ||
1086 | FILE_ERROR(ERRNO, rc * 10 - 8); | ||
1087 | } | ||
1088 | |||
1089 | fileop_onrename_internal(&oldstr, is_move ? &old_fileinfo : NULL, | ||
1090 | &newinfo.parentinfo, newname); | ||
1091 | |||
1092 | file_error: | ||
1093 | /* for now, there is nothing to fail upon closing the old stream */ | ||
1094 | if (open1rc >= 0) | ||
1095 | close_stream_internal(&oldstr); | ||
1096 | |||
1097 | /* the 'new' stream could fail to close cleanly because it became | ||
1098 | impossible to remove its data if this was an overwrite operation */ | ||
1099 | if (open2rc >= 0) | ||
1100 | { | ||
1101 | int rc2 = close_stream_internal(&newstr); | ||
1102 | if (rc2 < 0 && rc >= 0) | ||
830 | { | 1103 | { |
831 | pfile->busy = false; /* mark as available, no further action */ | 1104 | DEBUGF("Success but failed closing new: %d\n", rc2); |
832 | closed++; | 1105 | rc = rc2 * 10 - 9; |
833 | } | 1106 | } |
834 | } | 1107 | } |
835 | return closed; /* return how many we did */ | 1108 | |
1109 | file_internal_unlock_WRITER(); | ||
1110 | return rc; | ||
1111 | } | ||
1112 | |||
1113 | |||
1114 | /** Extensions **/ | ||
1115 | |||
1116 | /* get the binary size of a file (in bytes) */ | ||
1117 | off_t filesize(int fildes) | ||
1118 | { | ||
1119 | struct filestr_desc * const file = GET_FILESTR(READER, fildes); | ||
1120 | if (!file) | ||
1121 | FILE_ERROR_RETURN(ERRNO, -1); | ||
1122 | |||
1123 | off_t rc; | ||
1124 | file_size_t size = *file->sizep; | ||
1125 | |||
1126 | if (size > FILE_SIZE_MAX) | ||
1127 | FILE_ERROR(EOVERFLOW, -2); | ||
1128 | |||
1129 | rc = (off_t)size; | ||
1130 | file_error: | ||
1131 | RELEASE_FILESTR(READER, file); | ||
1132 | return rc; | ||
1133 | } | ||
1134 | |||
1135 | /* test if two file descriptors refer to the same file */ | ||
1136 | int fsamefile(int fildes1, int fildes2) | ||
1137 | { | ||
1138 | struct filestr_desc * const file1 = GET_FILESTR(WRITER, fildes1); | ||
1139 | if (!file1) | ||
1140 | FILE_ERROR_RETURN(ERRNO, -1); | ||
1141 | |||
1142 | int rc = -2; | ||
1143 | |||
1144 | struct filestr_desc * const file2 = get_filestr(fildes2); | ||
1145 | if (file2) | ||
1146 | rc = file1->stream.bindp == file2->stream.bindp ? 1 : 0; | ||
1147 | |||
1148 | RELEASE_FILESTR(WRITER, file1); | ||
1149 | return rc; | ||
1150 | } | ||
1151 | |||
1152 | /* tell the relationship of path1 to path2 */ | ||
1153 | int relate(const char *path1, const char *path2) | ||
1154 | { | ||
1155 | /* this is basically what rename() does but reduced to the relationship | ||
1156 | determination */ | ||
1157 | DEBUGF("relate(path1=\"%s\",path2=\"%s\")\n", path1, path2); | ||
1158 | |||
1159 | int rc, open1rc = -1, open2rc = -1; | ||
1160 | struct filestr_base str1, str2; | ||
1161 | struct path_component_info info1, info2; | ||
1162 | |||
1163 | file_internal_lock_WRITER(); | ||
1164 | |||
1165 | open1rc = open_stream_internal(path1, FF_ANYTYPE, &str1, &info1); | ||
1166 | if (open1rc <= 0) | ||
1167 | { | ||
1168 | DEBUGF("Failed opening path1: %d\n", rc); | ||
1169 | if (open1rc < 0) | ||
1170 | FILE_ERROR(ERRNO, open1rc * 10 - 1); | ||
1171 | else | ||
1172 | FILE_ERROR(ENOENT, -1); | ||
1173 | } | ||
1174 | |||
1175 | info2.prefixp = str1.infop; | ||
1176 | open2rc = open_stream_internal(path2, FF_ANYTYPE | FF_CHECKPREFIX, | ||
1177 | &str2, &info2); | ||
1178 | if (open2rc < 0) | ||
1179 | { | ||
1180 | DEBUGF("Failed opening path2: %d\n", rc); | ||
1181 | FILE_ERROR(ERRNO, open2rc * 10 - 2); | ||
1182 | } | ||
1183 | |||
1184 | rc = RELATE_DIFFERENT; | ||
1185 | |||
1186 | if (open2rc > 0) | ||
1187 | { | ||
1188 | if (str1.bindp == str2.bindp) | ||
1189 | rc = RELATE_SAME; | ||
1190 | else if (info2.attr & ATTR_PREFIX) | ||
1191 | rc = RELATE_PREFIX; | ||
1192 | } | ||
1193 | else /* open2rc == 0 */ | ||
1194 | { | ||
1195 | /* path1 existing and path2's final part not can only be a prefix or | ||
1196 | different */ | ||
1197 | if (info2.attr & ATTR_PREFIX) | ||
1198 | rc = RELATE_PREFIX; | ||
1199 | } | ||
1200 | |||
1201 | file_error: | ||
1202 | if (open1rc >= 0) | ||
1203 | close_stream_internal(&str1); | ||
1204 | |||
1205 | if (open2rc >= 0) | ||
1206 | close_stream_internal(&str2); | ||
1207 | |||
1208 | file_internal_unlock_WRITER(); | ||
1209 | return rc; | ||
1210 | } | ||
1211 | |||
1212 | /* test file or directory existence */ | ||
1213 | bool file_exists(const char *path) | ||
1214 | { | ||
1215 | file_internal_lock_WRITER(); | ||
1216 | bool rc = test_stream_exists_internal(path, FF_ANYTYPE) > 0; | ||
1217 | file_internal_unlock_WRITER(); | ||
1218 | return rc; | ||
836 | } | 1219 | } |