summaryrefslogtreecommitdiff
path: root/firmware/common/file.c
diff options
context:
space:
mode:
authorMichael Sevakis <jethead71@rockbox.org>2013-08-05 22:02:45 -0400
committerMichael Sevakis <jethead71@rockbox.org>2014-08-30 03:48:23 +0200
commit7d1a47cf13726c95ac46027156cc12dd9da5b855 (patch)
treeeb20d07656806479a8e1fea25887a490ea30d1d8 /firmware/common/file.c
parent95a4c3afcd53a1f8b835dec33de51f9c304de4d9 (diff)
downloadrockbox-7d1a47cf13726c95ac46027156cc12dd9da5b855.tar.gz
rockbox-7d1a47cf13726c95ac46027156cc12dd9da5b855.zip
Rewrite filesystem code (WIP)
This patch redoes the filesystem code from the FAT driver up to the clipboard code in onplay.c. Not every aspect of this is finished therefore it is still "WIP". I don't wish to do too much at once (haha!). What is left to do is get dircache back in the sim and find an implementation for the dircache indicies in the tagcache and playlist code or do something else that has the same benefit. Leaving these out for now does not make anything unusable. All the basics are done. Phone app code should probably get vetted (and app path handling just plain rewritten as environment expansions); the SDL app and Android run well. Main things addressed: 1) Thread safety: There is none right now in the trunk code. Most of what currently works is luck when multiple threads are involved or multiple descriptors to the same file are open. 2) POSIX compliance: Many of the functions behave nothing like their counterparts on a host system. This leads to inconsistent code or very different behavior from native to hosted. One huge offender was rename(). Going point by point would fill a book. 3) Actual running RAM usage: Many targets will use less RAM and less stack space (some more RAM because I upped the number of cache buffers for large memory). There's very little memory lying fallow in rarely-used areas (see 'Key core changes' below). Also, all targets may open the same number of directory streams whereas before those with less than 8MB RAM were limited to 8, not 12 implying those targets will save slightly less. 4) Performance: The test_disk plugin shows markedly improved performance, particularly in the area of (uncached) directory scanning, due partly to more optimal directory reading and to a better sector cache algorithm. Uncached times tend to be better while there is a bit of a slowdown in dircache due to it being a bit heavier of an implementation. It's not noticeable by a human as far as I can say. Key core changes: 1) Files and directories share core code and data structures. 2) The filesystem code knows which descriptors refer to same file. This ensures that changes from one stream are appropriately reflected in every open descriptor for that file (fileobj_mgr.c). 3) File and directory cache buffers are borrowed from the main sector cache. This means that when they are not in use by a file, they are not wasted, but used for the cache. Most of the time, only a few of them are needed. It also means that adding more file and directory handles is less expensive. All one must do in ensure a large enough cache to borrow from. 4) Relative path components are supported and the namespace is unified. It does not support full relative paths to an implied current directory; what is does support is use of "." and "..". Adding the former would not be very difficult. The namespace is unified in the sense that volumes may be specified several times along with relative parts, e.g.: "/<0>/foo/../../<1>/bar" :<=> "/<1>/bar". 5) Stack usage is down due to sharing of data, static allocation and less duplication of strings on the stack. This requires more serialization than I would like but since the number of threads is limited to a low number, the tradoff in favor of the stack seems reasonable. 6) Separates and heirarchicalizes (sic) the SIM and APP filesystem code. SIM path and volume handling is just like the target. Some aspects of the APP file code get more straightforward (e.g. no path hashing is needed). Dircache: Deserves its own section. Dircache is new but pays homage to the old. The old one was not compatible and so it, since it got redone, does all the stuff it always should have done such as: 1) It may be update and used at any time during the build process. No longer has one to wait for it to finish building to do basic file management (create, remove, rename, etc.). 2) It does not need to be either fully scanned or completely disabled; it can be incomplete (i.e. overfilled, missing paths), still be of benefit and be correct. 3) Handles mounting and dismounting of individual volumes which means a full rebuild is not needed just because you pop a new SD card in the slot. Now, because it reuses its freed entry data, may rebuild only that volume. 4) Much more fundamental to the file code. When it is built, it is the keeper of the master file list whether enabled or not ("disabled" is just a state of the cache). Its must always to ready to be started and bind all streams opened prior to being enabled. 5) Maintains any short filenames in OEM format which means that it does not need to be rebuilt when changing the default codepage. Miscellaneous Compatibility: 1) Update any other code that would otherwise not work such as the hotswap mounting code in various card drivers. 2) File management: Clipboard needed updating because of the behavioral changes. Still needs a little more work on some finer points. 3) Remove now-obsolete functionality such as the mutex's "no preempt" flag (which was only for the prior FAT driver). 4) struct dirinfo uses time_t rather than raw FAT directory entry time fields. I plan to follow up on genericizing everything there (i.e. no FAT attributes). 5) unicode.c needed some redoing so that the file code does not try try to load codepages during a scan, which is actually a problem with the current code. The default codepage, if any is required, is now kept in RAM separarately (bufalloced) from codepages specified to iso_decode() (which must not be bufalloced because the conversion may be done by playback threads). Brings with it some additional reusable core code: 1) Revised file functions: Reusable code that does things such as safe path concatenation and parsing without buffer limitations or data duplication. Variants that copy or alter the input path may be based off these. To do: 1) Put dircache functionality back in the sim. Treating it internally as a different kind of file system seems the best approach at this time. 2) Restore use of dircache indexes in the playlist and database or something effectively the same. Since the cache doesn't have to be complete in order to be used, not getting a hit on the cache doesn't unambiguously say if the path exists or not. Change-Id: Ia30f3082a136253e3a0eae0784e3091d138915c8 Reviewed-on: http://gerrit.rockbox.org/566 Reviewed-by: Michael Sevakis <jethead71@rockbox.org> Tested: Michael Sevakis <jethead71@rockbox.org>
Diffstat (limited to 'firmware/common/file.c')
-rw-r--r--firmware/common/file.c1637
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 */
39static 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. 47static 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
41struct 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
54static 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 */
80static 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
56static int flush_cache(int fd); 92 DEBUGF("Too many files open\n");
93 return -1;
94}
57 95
58int file_creat(const char *pathname) 96/* return the file size in sectors */
97static 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
63static int open_internal(const char* pathname, int flags, bool use_cache) 108/* flush a dirty cache buffer */
109static 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;
135file_error:
136 DEBUGF("Failed flushing cache: %d\n", rc);
137 return rc;
138}
139
140static 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; 147static 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, 186file_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; 192void 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 */
211static 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
233int file_open(const char* pathname, int flags) 266file_error:
234{ 267 return rc;
235 /* By default, use the dircache if available. */
236 return open_internal(pathname, flags, true);
237} 268}
238 269
239int close(int fd) 270/* flush back all outstanding writes to the file */
271static 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; 308file_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 */
325static 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; 339file_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
268int fsync(int fd) 346/* actually do the open gruntwork */
347static 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
314int 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) 432file_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
343int rename(const char* path, const char* newpath) 439/* allocate a file descriptor, if needed, assemble stream flags and open
440 a new stream */
441static 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); 493file_error:
433 if (rc<0) { 494 return rc;
434 errno = EIO; 495}
435 return rc * 10 - 9;
436 }
437 496
438 return 0; 497static 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
441int ftruncate(int fd, off_t size) 506/* fill a cache buffer with a new sector */
507static 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;
546file_error:
547 DEBUGF("Failed caching sector: %d\n", rc);
548 return rc;
468} 549}
469 550
470static int flush_cache(int fd) 551/* read or write to part or all of the cache buffer */
552static 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 */
573static 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
497static int readwrite(int fd, void* buf, long count, bool write) 594/* read from or write to the file; back end to read() and write() */
595static 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; 752file_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
702ssize_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 */
784int 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
711ssize_t read(int fd, void* buf, size_t count) 789
790/** POSIX **/
791
792/* open a file */
793int 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 */
800int 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
717off_t lseek(int fd, off_t offset, int whence) 806/* close a file descriptor */
807int 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: 827file_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 */
833int 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) || 859file_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) { 865int 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; 885file_error:
886 RELEASE_FILESTR(WRITER, file);
887 return rc;
798} 888}
799 889
800off_t filesize(int fd) 890/* move the read/write file offset */
891off_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
903file_error:
904 RELEASE_FILESTR(READER, file);
905 return rc;
906}
907
908/* read from a file */
909ssize_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
928file_error:
929 RELEASE_FILESTR(READER, file);
930 return rc;
931}
932
933/* write on a file */
934ssize_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
953file_error:
954 RELEASE_FILESTR(READER, file);
955 return rc;
814} 956}
815 957
958/* remove a file */
959int 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 */
818int release_files(int volume) 970int 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
1092file_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) */
1117off_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;
1130file_error:
1131 RELEASE_FILESTR(READER, file);
1132 return rc;
1133}
1134
1135/* test if two file descriptors refer to the same file */
1136int 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 */
1153int 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
1201file_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 */
1213bool 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}