a9f36efa62
This eliminates the dependence on a special struct since we were only using the modtime anyway. But it no longer fits any known standard APIs so I have converted it to our own extension instead. This can still be adapted to existing hosted APIs if the need arises. Change-Id: Ic8800698ddfd3a1a48b7cf921c0d0f865302d034
1267 lines
35 KiB
C
1267 lines
35 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2002 by Björn Stenberg
|
|
* Copyright (C) 2014 by Michael Sevakis
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
#define RB_FILESYSTEM_OS
|
|
#include "config.h"
|
|
#include "system.h"
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include "debug.h"
|
|
#include "file.h"
|
|
#include "fileobj_mgr.h"
|
|
#include "disk_cache.h"
|
|
#include "dircache_redirect.h"
|
|
#include "string-extra.h"
|
|
|
|
/**
|
|
* These functions provide a roughly POSIX-compatible file I/O API.
|
|
*/
|
|
|
|
/* structure used for open file descriptors */
|
|
static struct filestr_desc
|
|
{
|
|
struct filestr_base stream; /* basic stream info (first!) */
|
|
file_size_t offset; /* current offset for stream */
|
|
file_size_t *sizep; /* shortcut to file size in fileobj */
|
|
} open_streams[MAX_OPEN_FILES];
|
|
|
|
/* check and return a struct filestr_desc* from a file descriptor number */
|
|
static struct filestr_desc * get_filestr(int fildes)
|
|
{
|
|
struct filestr_desc *file = &open_streams[fildes];
|
|
|
|
if ((unsigned int)fildes >= MAX_OPEN_FILES)
|
|
file = NULL;
|
|
else if (file->stream.flags & FDO_BUSY)
|
|
return file;
|
|
|
|
DEBUGF("fildes %d: bad file number\n", fildes);
|
|
errno = (file && (file->stream.flags & FD_NONEXIST)) ? ENXIO : EBADF;
|
|
return NULL;
|
|
}
|
|
|
|
#define GET_FILESTR(type, fildes) \
|
|
({ \
|
|
file_internal_lock_##type(); \
|
|
struct filestr_desc * _file = get_filestr(fildes); \
|
|
if (_file) \
|
|
FILESTR_LOCK(type, &_file->stream); \
|
|
else \
|
|
file_internal_unlock_##type(); \
|
|
_file; \
|
|
})
|
|
|
|
/* release the lock on the filestr_desc* */
|
|
#define RELEASE_FILESTR(type, file) \
|
|
({ \
|
|
FILESTR_UNLOCK(type, &(file)->stream); \
|
|
file_internal_unlock_##type(); \
|
|
})
|
|
|
|
/* find a free file descriptor */
|
|
static int alloc_filestr(struct filestr_desc **filep)
|
|
{
|
|
for (int fildes = 0; fildes < MAX_OPEN_FILES; fildes++)
|
|
{
|
|
struct filestr_desc *file = &open_streams[fildes];
|
|
if (!file->stream.flags)
|
|
{
|
|
*filep = file;
|
|
return fildes;
|
|
}
|
|
}
|
|
|
|
DEBUGF("Too many files open\n");
|
|
return -1;
|
|
}
|
|
|
|
/* return the file size in sectors */
|
|
static inline unsigned long filesize_sectors(file_size_t size)
|
|
{
|
|
/* overflow proof whereas "(x + y - 1) / y" is not */
|
|
unsigned long numsectors = size / SECTOR_SIZE;
|
|
|
|
if (size % SECTOR_SIZE)
|
|
numsectors++;
|
|
|
|
return numsectors;
|
|
}
|
|
|
|
/* flush a dirty cache buffer */
|
|
static int flush_cache(struct filestr_desc *file)
|
|
{
|
|
int rc;
|
|
struct filestr_cache *cachep = file->stream.cachep;
|
|
|
|
DEBUGF("Flushing dirty sector cache (%lu)\n", cachep->sector);
|
|
|
|
if (fat_query_sectornum(&file->stream.fatstr) != cachep->sector)
|
|
{
|
|
/* get on the correct sector */
|
|
rc = fat_seek(&file->stream.fatstr, cachep->sector);
|
|
if (rc < 0)
|
|
FILE_ERROR(EIO, rc * 10 - 1);
|
|
}
|
|
|
|
rc = fat_readwrite(&file->stream.fatstr, 1, cachep->buffer, true);
|
|
if (rc < 0)
|
|
{
|
|
if (rc == FAT_RC_ENOSPC)
|
|
FILE_ERROR(ENOSPC, RC);
|
|
else
|
|
FILE_ERROR(EIO, rc * 10 - 2);
|
|
}
|
|
|
|
cachep->flags = 0;
|
|
return 1;
|
|
file_error:
|
|
DEBUGF("Failed flushing cache: %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
static void discard_cache(struct filestr_desc *file)
|
|
{
|
|
struct filestr_cache *const cachep = file->stream.cachep;
|
|
cachep->flags = 0;
|
|
}
|
|
|
|
/* set the file pointer */
|
|
static off_t lseek_internal(struct filestr_desc *file, off_t offset,
|
|
int whence)
|
|
{
|
|
off_t rc;
|
|
file_size_t pos;
|
|
|
|
file_size_t size = MIN(*file->sizep, FILE_SIZE_MAX);
|
|
|
|
switch (whence)
|
|
{
|
|
case SEEK_SET:
|
|
if (offset < 0 || (file_size_t)offset > size)
|
|
FILE_ERROR(EINVAL, -1);
|
|
|
|
pos = offset;
|
|
break;
|
|
|
|
case SEEK_CUR:
|
|
if ((offset < 0 && (file_size_t)-offset > file->offset) ||
|
|
(offset > 0 && (file_size_t)offset > size - file->offset))
|
|
FILE_ERROR(EINVAL, -1);
|
|
|
|
pos = file->offset + offset;
|
|
break;
|
|
|
|
case SEEK_END:
|
|
if (offset > 0 || (file_size_t)-offset > size)
|
|
FILE_ERROR(EINVAL, -1);
|
|
|
|
pos = size + offset;
|
|
break;
|
|
|
|
default:
|
|
FILE_ERROR(EINVAL, -1);
|
|
}
|
|
|
|
file->offset = pos;
|
|
|
|
return pos;
|
|
file_error:
|
|
return rc;
|
|
}
|
|
|
|
/* Handle syncing all file's streams to the truncation */
|
|
static void handle_truncate(struct filestr_desc * const file, file_size_t size)
|
|
{
|
|
unsigned long filesectors = filesize_sectors(size);
|
|
|
|
struct filestr_base *s = NULL;
|
|
while ((s = fileobj_get_next_stream(&file->stream, s)))
|
|
{
|
|
/* caches with data beyond new extents are invalid */
|
|
unsigned long sector = s->cachep->sector;
|
|
if (sector != INVALID_SECNUM && sector >= filesectors)
|
|
filestr_discard_cache(s);
|
|
|
|
/* files outside bounds must be rewound */
|
|
if (fat_query_sectornum(&s->fatstr) > filesectors)
|
|
fat_seek_to_stream(&s->fatstr, &file->stream.fatstr);
|
|
|
|
/* clip file offset too if needed */
|
|
struct filestr_desc *f = (struct filestr_desc *)s;
|
|
if (f->offset > size)
|
|
f->offset = size;
|
|
}
|
|
}
|
|
|
|
/* truncate the file to the specified length */
|
|
static int ftruncate_internal(struct filestr_desc *file, file_size_t size,
|
|
bool write_now)
|
|
{
|
|
int rc = 0, rc2 = 1;
|
|
|
|
file_size_t cursize = *file->sizep;
|
|
file_size_t truncsize = MIN(size, cursize);
|
|
|
|
if (write_now)
|
|
{
|
|
unsigned long sector = filesize_sectors(truncsize);
|
|
struct filestr_cache *const cachep = file->stream.cachep;
|
|
|
|
if (cachep->flags == (FSC_NEW|FSC_DIRTY) &&
|
|
cachep->sector + 1 == sector)
|
|
{
|
|
/* sector created but may have never been added to the cluster
|
|
chain; flush it now or the subsequent may fail */
|
|
rc2 = flush_cache(file);
|
|
if (rc2 == FAT_RC_ENOSPC)
|
|
{
|
|
/* no space left on device; further truncation needed */
|
|
discard_cache(file);
|
|
truncsize = ALIGN_DOWN(truncsize - 1, SECTOR_SIZE);
|
|
sector--;
|
|
rc = rc2;
|
|
}
|
|
else if (rc2 < 0)
|
|
FILE_ERROR(ERRNO, rc2 * 10 - 1);
|
|
}
|
|
|
|
rc2 = fat_seek(&file->stream.fatstr, sector);
|
|
if (rc2 < 0)
|
|
FILE_ERROR(EIO, rc2 * 10 - 2);
|
|
|
|
rc2 = fat_truncate(&file->stream.fatstr);
|
|
if (rc2 < 0)
|
|
FILE_ERROR(EIO, rc2 * 10 - 3);
|
|
|
|
/* never needs to be done this way again since any data beyond the
|
|
cached size is now gone */
|
|
fileobj_change_flags(&file->stream, 0, FO_TRUNC);
|
|
}
|
|
/* else just change the cached file size */
|
|
|
|
if (truncsize < cursize)
|
|
{
|
|
*file->sizep = truncsize;
|
|
handle_truncate(file, truncsize);
|
|
}
|
|
|
|
/* if truncation was partially successful, it effectively destroyed
|
|
everything after the truncation point; still, indicate failure
|
|
after adjusting size */
|
|
if (rc2 == 0)
|
|
FILE_ERROR(EIO, -4);
|
|
else if (rc2 < 0)
|
|
FILE_ERROR(ERRNO, rc2);
|
|
|
|
file_error:
|
|
return rc;
|
|
}
|
|
|
|
/* flush back all outstanding writes to the file */
|
|
static int fsync_internal(struct filestr_desc *file)
|
|
{
|
|
/* call only when holding WRITER lock (updates directory entries) */
|
|
int rc = 0;
|
|
|
|
file_size_t size = *file->sizep;
|
|
unsigned int foflags = fileobj_get_flags(&file->stream);
|
|
|
|
/* flush sector cache? */
|
|
struct filestr_cache *const cachep = file->stream.cachep;
|
|
if (cachep->flags & FSC_DIRTY)
|
|
{
|
|
int rc2 = flush_cache(file);
|
|
if (rc2 == FAT_RC_ENOSPC && (cachep->flags & FSC_NEW))
|
|
{
|
|
/* no space left on device so this must be dropped */
|
|
discard_cache(file);
|
|
size = ALIGN_DOWN(size - 1, SECTOR_SIZE);
|
|
foflags |= FO_TRUNC;
|
|
rc = rc2;
|
|
}
|
|
else if (rc2 < 0)
|
|
FILE_ERROR(ERRNO, rc2 * 10 - 1);
|
|
}
|
|
|
|
/* truncate? */
|
|
if (foflags & FO_TRUNC)
|
|
{
|
|
int rc2 = ftruncate_internal(file, size, rc == 0);
|
|
if (rc2 < 0)
|
|
FILE_ERROR(ERRNO, rc2 * 10 - 2);
|
|
}
|
|
|
|
file_error:;
|
|
/* tie up all loose ends (try to close the file even if failing) */
|
|
int rc2 = fat_closewrite(&file->stream.fatstr, size,
|
|
get_dir_fatent_dircache());
|
|
if (rc2 >= 0)
|
|
fileop_onsync_internal(&file->stream); /* dir_fatent is implicit arg */
|
|
|
|
if (rc2 < 0 && rc >= 0)
|
|
{
|
|
errno = EIO;
|
|
rc = rc2 * 10 - 3;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* finish with the file and free resources */
|
|
static int close_internal(struct filestr_desc *file)
|
|
{
|
|
/* call only when holding WRITER lock (updates directory entries) */
|
|
int rc;
|
|
|
|
if ((file->stream.flags & (FD_WRITE|FD_NONEXIST)) == FD_WRITE)
|
|
{
|
|
rc = fsync_internal(file);
|
|
if (rc < 0)
|
|
FILE_ERROR(ERRNO, rc * 10 - 1);
|
|
}
|
|
|
|
rc = 0;
|
|
file_error:;
|
|
int rc2 = close_stream_internal(&file->stream);
|
|
if (rc2 < 0 && rc >= 0)
|
|
rc = rc2 * 10 - 2;
|
|
return rc;
|
|
}
|
|
|
|
/* actually do the open gruntwork */
|
|
static int open_internal_inner2(const char *path,
|
|
struct filestr_desc *file,
|
|
unsigned int callflags,
|
|
int oflag)
|
|
{
|
|
int rc;
|
|
|
|
struct path_component_info compinfo;
|
|
|
|
if (oflag & O_CREAT)
|
|
callflags |= FF_PARENTINFO;
|
|
|
|
rc = open_stream_internal(path, callflags, &file->stream, &compinfo);
|
|
if (rc < 0)
|
|
{
|
|
DEBUGF("Open failed: %d\n", rc);
|
|
FILE_ERROR_RETURN(ERRNO, rc * 10 - 1);
|
|
}
|
|
|
|
bool created = false;
|
|
|
|
if (rc > 0)
|
|
{
|
|
if (oflag & O_EXCL)
|
|
{
|
|
DEBUGF("File exists\n");
|
|
FILE_ERROR(EEXIST, -2);
|
|
}
|
|
|
|
if (compinfo.attr & ATTR_DIRECTORY)
|
|
{
|
|
if ((callflags & FD_WRITE) || !(callflags & FF_ANYTYPE))
|
|
{
|
|
DEBUGF("File is a directory\n");
|
|
FILE_ERROR(EISDIR, -3);
|
|
}
|
|
|
|
compinfo.filesize = MAX_DIRECTORY_SIZE; /* allow file ops */
|
|
}
|
|
}
|
|
else if (oflag & O_CREAT)
|
|
{
|
|
if (compinfo.attr & ATTR_DIRECTORY)
|
|
{
|
|
DEBUGF("File is a directory\n");
|
|
FILE_ERROR(EISDIR, -5);
|
|
}
|
|
|
|
/* not found; try to create it */
|
|
|
|
callflags &= ~FO_TRUNC;
|
|
rc = create_stream_internal(&compinfo.parentinfo, compinfo.name,
|
|
compinfo.length, ATTR_NEW_FILE, callflags,
|
|
&file->stream);
|
|
if (rc < 0)
|
|
FILE_ERROR(ERRNO, rc * 10 - 6);
|
|
|
|
created = true;
|
|
}
|
|
else
|
|
{
|
|
DEBUGF("File not found\n");
|
|
FILE_ERROR(ENOENT, -7);
|
|
}
|
|
|
|
fat_rewind(&file->stream.fatstr);
|
|
file->sizep = fileobj_get_sizep(&file->stream);
|
|
file->offset = 0;
|
|
|
|
if (!created)
|
|
{
|
|
/* size from storage applies to first stream only otherwise it's
|
|
already up to date */
|
|
const bool first = fileobj_get_flags(&file->stream) & FO_SINGLE;
|
|
if (first)
|
|
*file->sizep = compinfo.filesize;
|
|
|
|
if (callflags & FO_TRUNC)
|
|
{
|
|
/* if the file is kind of "big" then free some space now */
|
|
rc = ftruncate_internal(file, 0, *file->sizep >= O_TRUNC_THRESH);
|
|
if (rc < 0)
|
|
{
|
|
DEBUGF("O_TRUNC failed: %d\n", rc);
|
|
FILE_ERROR(ERRNO, rc * 10 - 4);
|
|
}
|
|
}
|
|
}
|
|
|
|
rc = 0;
|
|
file_error:
|
|
if (rc < 0)
|
|
close_stream_internal(&file->stream);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* allocate a file descriptor, if needed, assemble stream flags and open
|
|
a new stream */
|
|
static int open_internal_inner1(const char *path, int oflag,
|
|
unsigned int callflags)
|
|
{
|
|
DEBUGF("%s(path=\"%s\",oflag=%X,callflags=%X)\n", __func__,
|
|
path, oflag, callflags);
|
|
|
|
int rc;
|
|
|
|
struct filestr_desc *file;
|
|
int fildes = alloc_filestr(&file);
|
|
if (fildes < 0)
|
|
FILE_ERROR(EMFILE, -1);
|
|
|
|
callflags &= ~FDO_MASK;
|
|
|
|
if (oflag & O_ACCMODE)
|
|
{
|
|
callflags |= FD_WRITE;
|
|
|
|
if ((oflag & O_ACCMODE) == O_WRONLY)
|
|
callflags |= FD_WRONLY;
|
|
|
|
if (oflag & O_APPEND)
|
|
callflags |= FD_APPEND;
|
|
|
|
if (oflag & O_TRUNC)
|
|
callflags |= FO_TRUNC;
|
|
}
|
|
else if (oflag & O_TRUNC)
|
|
{
|
|
/* O_TRUNC requires write mode */
|
|
DEBUGF("No write mode but have O_TRUNC\n");
|
|
FILE_ERROR(EINVAL, -2);
|
|
}
|
|
|
|
/* O_CREAT and O_APPEND are fine without write mode
|
|
* for the former, an empty file is created but no data may be written
|
|
* for the latter, no append will be allowed anyway */
|
|
if (!(oflag & O_CREAT))
|
|
oflag &= ~O_EXCL; /* result is undefined: we choose "ignore" */
|
|
|
|
rc = open_internal_inner2(path, file, callflags, oflag);
|
|
if (rc < 0)
|
|
FILE_ERROR(ERRNO, rc * 10 - 3);
|
|
|
|
return fildes;
|
|
|
|
file_error:
|
|
return rc;
|
|
}
|
|
|
|
static int open_internal_locked(const char *path, int oflag,
|
|
unsigned int callflags)
|
|
{
|
|
file_internal_lock_WRITER();
|
|
int rc = open_internal_inner1(path, oflag, callflags);
|
|
file_internal_unlock_WRITER();
|
|
return rc;
|
|
}
|
|
|
|
/* fill a cache buffer with a new sector */
|
|
static int readwrite_fill_cache(struct filestr_desc *file, unsigned long sector,
|
|
unsigned long filesectors, bool write)
|
|
{
|
|
/* sector != cachep->sector should have been checked by now */
|
|
|
|
int rc;
|
|
struct filestr_cache *cachep = filestr_get_cache(&file->stream);
|
|
|
|
if (cachep->flags & FSC_DIRTY)
|
|
{
|
|
rc = flush_cache(file);
|
|
if (rc < 0)
|
|
FILE_ERROR(ERRNO, rc * 10 - 1);
|
|
}
|
|
|
|
if (fat_query_sectornum(&file->stream.fatstr) != sector)
|
|
{
|
|
/* get on the correct sector */
|
|
rc = fat_seek(&file->stream.fatstr, sector);
|
|
if (rc < 0)
|
|
FILE_ERROR(EIO, rc * 10 - 2);
|
|
}
|
|
|
|
if (!write || sector < filesectors)
|
|
{
|
|
/* only reading or this sector would have been flushed if the cache
|
|
was previously needed for a different sector */
|
|
rc = fat_readwrite(&file->stream.fatstr, 1, cachep->buffer, false);
|
|
if (rc < 0)
|
|
FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO, rc * 10 - 3);
|
|
}
|
|
else
|
|
{
|
|
/* create a fresh, shiny, new sector with that new sector smell */
|
|
cachep->flags = FSC_NEW;
|
|
}
|
|
|
|
cachep->sector = sector;
|
|
return 1;
|
|
file_error:
|
|
DEBUGF("Failed caching sector: %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* read or write to part or all of the cache buffer */
|
|
static inline void readwrite_cache(struct filestr_cache *cachep, void *buf,
|
|
unsigned long secoffset, size_t nbyte,
|
|
bool write)
|
|
{
|
|
void *dst, *cbufp = cachep->buffer + secoffset;
|
|
|
|
if (write)
|
|
{
|
|
dst = cbufp;
|
|
cachep->flags |= FSC_DIRTY;
|
|
}
|
|
else
|
|
{
|
|
dst = buf;
|
|
buf = cbufp;
|
|
}
|
|
|
|
memcpy(dst, buf, nbyte);
|
|
}
|
|
|
|
/* read or write a partial sector using the file's cache */
|
|
static inline ssize_t readwrite_partial(struct filestr_desc *file,
|
|
struct filestr_cache *cachep,
|
|
unsigned long sector,
|
|
unsigned long secoffset,
|
|
void *buf,
|
|
size_t nbyte,
|
|
unsigned long filesectors,
|
|
unsigned int flags)
|
|
{
|
|
if (sector != cachep->sector)
|
|
{
|
|
/* wrong sector in buffer */
|
|
int rc = readwrite_fill_cache(file, sector, filesectors, flags);
|
|
if (rc <= 0)
|
|
return rc;
|
|
}
|
|
|
|
readwrite_cache(cachep, buf, secoffset, nbyte, flags);
|
|
return nbyte;
|
|
}
|
|
|
|
/* read from or write to the file; back end to read() and write() */
|
|
static ssize_t readwrite(struct filestr_desc *file, void *buf, size_t nbyte,
|
|
bool write)
|
|
{
|
|
DEBUGF("readwrite(%p,%lx,%lu,%s)\n",
|
|
file, (long)buf, (unsigned long)nbyte, write ? "write" : "read");
|
|
|
|
const file_size_t size = *file->sizep;
|
|
file_size_t filerem;
|
|
|
|
if (write)
|
|
{
|
|
/* if opened in append mode, move pointer to end */
|
|
if (file->stream.flags & FD_APPEND)
|
|
file->offset = MIN(size, FILE_SIZE_MAX);
|
|
|
|
filerem = FILE_SIZE_MAX - file->offset;
|
|
}
|
|
else
|
|
{
|
|
/* limit to maximum possible offset (EOF or FILE_SIZE_MAX) */
|
|
filerem = MIN(size, FILE_SIZE_MAX) - file->offset;
|
|
}
|
|
|
|
if (nbyte > filerem)
|
|
{
|
|
nbyte = filerem;
|
|
if (nbyte > 0)
|
|
{}
|
|
else if (write)
|
|
FILE_ERROR_RETURN(EFBIG, -1); /* would get too large */
|
|
else if (file->offset >= FILE_SIZE_MAX)
|
|
FILE_ERROR_RETURN(EOVERFLOW, -2); /* can't read here */
|
|
}
|
|
|
|
if (nbyte == 0)
|
|
return 0;
|
|
|
|
int rc = 0;
|
|
|
|
struct filestr_cache * const cachep = file->stream.cachep;
|
|
void * const bufstart = buf;
|
|
|
|
const unsigned long filesectors = filesize_sectors(size);
|
|
unsigned long sector = file->offset / SECTOR_SIZE;
|
|
unsigned long sectoroffs = file->offset % SECTOR_SIZE;
|
|
|
|
/* any head bytes? */
|
|
if (sectoroffs)
|
|
{
|
|
size_t headbytes = MIN(nbyte, SECTOR_SIZE - sectoroffs);
|
|
rc = readwrite_partial(file, cachep, sector, sectoroffs, buf, headbytes,
|
|
filesectors, write);
|
|
if (rc <= 0)
|
|
{
|
|
if (rc < 0)
|
|
FILE_ERROR(ERRNO, rc * 10 - 3);
|
|
|
|
nbyte = 0; /* eof, skip the rest */
|
|
}
|
|
else
|
|
{
|
|
buf += rc;
|
|
nbyte -= rc;
|
|
sector++; /* if nbyte goes to 0, the rest is skipped anyway */
|
|
}
|
|
}
|
|
|
|
/* read/write whole sectors right into/from the supplied buffer */
|
|
unsigned long sectorcount = nbyte / SECTOR_SIZE;
|
|
|
|
while (sectorcount)
|
|
{
|
|
unsigned long runlen = sectorcount;
|
|
|
|
/* if a cached sector is inside the transfer range, split the transfer
|
|
into two parts and use the cache for that sector to keep it coherent
|
|
without writeback */
|
|
if (UNLIKELY(cachep->sector >= sector &&
|
|
cachep->sector < sector + sectorcount))
|
|
{
|
|
runlen = cachep->sector - sector;
|
|
}
|
|
|
|
if (runlen)
|
|
{
|
|
if (fat_query_sectornum(&file->stream.fatstr) != sector)
|
|
{
|
|
/* get on the correct sector */
|
|
rc = 0;
|
|
|
|
/* If the dirty bit isn't set, we're somehow beyond the file
|
|
size and you can't explain _that_ */
|
|
if (sector >= filesectors && cachep->flags == (FSC_NEW|FSC_DIRTY))
|
|
{
|
|
rc = flush_cache(file);
|
|
if (rc < 0)
|
|
FILE_ERROR(ERRNO, rc * 10 - 4);
|
|
|
|
if (cachep->sector + 1 == sector)
|
|
rc = 1; /* if now ok, don't seek */
|
|
}
|
|
|
|
if (rc == 0)
|
|
{
|
|
rc = fat_seek(&file->stream.fatstr, sector);
|
|
if (rc < 0)
|
|
FILE_ERROR(EIO, rc * 10 - 5);
|
|
}
|
|
}
|
|
|
|
rc = fat_readwrite(&file->stream.fatstr, runlen, buf, write);
|
|
if (rc < 0)
|
|
{
|
|
DEBUGF("I/O error %sing %ld sectors\n",
|
|
write ? "writ" : "read", runlen);
|
|
FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO,
|
|
rc * 10 - 6);
|
|
}
|
|
else
|
|
{
|
|
buf += rc * SECTOR_SIZE;
|
|
nbyte -= rc * SECTOR_SIZE;
|
|
sector += rc;
|
|
sectorcount -= rc;
|
|
|
|
/* if eof, skip tail bytes */
|
|
if ((unsigned long)rc < runlen)
|
|
nbyte = 0;
|
|
|
|
if (!nbyte)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (UNLIKELY(sectorcount && sector == cachep->sector))
|
|
{
|
|
/* do this one sector with the cache */
|
|
readwrite_cache(cachep, buf, 0, SECTOR_SIZE, write);
|
|
buf += SECTOR_SIZE;
|
|
nbyte -= SECTOR_SIZE;
|
|
sector++;
|
|
sectorcount--;
|
|
}
|
|
}
|
|
|
|
/* any tail bytes? */
|
|
if (nbyte)
|
|
{
|
|
/* tail bytes always start at sector offset 0 */
|
|
rc = readwrite_partial(file, cachep, sector, 0, buf, nbyte,
|
|
filesectors, write);
|
|
if (rc < 0)
|
|
FILE_ERROR(ERRNO, rc * 10 - 7);
|
|
|
|
buf += rc;
|
|
}
|
|
|
|
file_error:;
|
|
#ifdef DEBUG
|
|
if (errno == ENOSPC)
|
|
DEBUGF("No space left on device\n");
|
|
#endif
|
|
|
|
size_t done = buf - bufstart;
|
|
if (done)
|
|
{
|
|
/* error or not, update the file offset and size if anything was
|
|
transferred */
|
|
file->offset += done;
|
|
DEBUGF("file offset: %ld\n", file->offset);
|
|
|
|
/* adjust file size to length written */
|
|
if (write && file->offset > size)
|
|
*file->sizep = file->offset;
|
|
|
|
if (rc > 0)
|
|
return done;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
/** Internal interface **/
|
|
|
|
/* open a file without codepage conversion during the directory search;
|
|
required to avoid any reentrancy when opening codepages and when scanning
|
|
directories internally, which could infinitely recurse and would corrupt
|
|
the static data */
|
|
int open_noiso_internal(const char *path, int oflag)
|
|
{
|
|
return open_internal_locked(path, oflag, FF_ANYTYPE | FF_NOISO);
|
|
}
|
|
|
|
void force_close_writer_internal(struct filestr_base *stream)
|
|
{
|
|
/* only we do writers so we know this is our guy */
|
|
close_internal((struct filestr_desc *)stream);
|
|
}
|
|
|
|
|
|
/** POSIX **/
|
|
|
|
/* open a file */
|
|
int open(const char *path, int oflag)
|
|
{
|
|
DEBUGF("open(path=\"%s\",oflag=%X)\n", path, (unsigned)oflag);
|
|
return open_internal_locked(path, oflag, FF_ANYTYPE);
|
|
}
|
|
|
|
/* create a new file or rewrite an existing one */
|
|
int creat(const char *path)
|
|
{
|
|
DEBUGF("creat(path=\"%s\")\n", path);
|
|
return open_internal_locked(path, O_WRONLY|O_CREAT|O_TRUNC, FF_ANYTYPE);
|
|
}
|
|
|
|
/* close a file descriptor */
|
|
int close(int fildes)
|
|
{
|
|
DEBUGF("close(fd=%d)\n", fildes);
|
|
|
|
int rc;
|
|
|
|
file_internal_lock_WRITER();
|
|
|
|
/* needs to work even if marked "nonexistant" */
|
|
struct filestr_desc *file = &open_streams[fildes];
|
|
if ((unsigned int)fildes >= MAX_OPEN_FILES || !file->stream.flags)
|
|
{
|
|
DEBUGF("filedes %d not open\n", fildes);
|
|
FILE_ERROR(EBADF, -2);
|
|
}
|
|
|
|
rc = close_internal(file);
|
|
if (rc < 0)
|
|
FILE_ERROR(ERRNO, rc * 10 - 3);
|
|
|
|
file_error:
|
|
file_internal_unlock_WRITER();
|
|
return rc;
|
|
}
|
|
|
|
/* truncate a file to a specified length */
|
|
int ftruncate(int fildes, off_t length)
|
|
{
|
|
DEBUGF("ftruncate(fd=%d,len=%ld)\n", fildes, (long)length);
|
|
|
|
struct filestr_desc * const file = GET_FILESTR(READER, fildes);
|
|
if (!file)
|
|
FILE_ERROR_RETURN(ERRNO, -1);
|
|
|
|
int rc;
|
|
|
|
if (!(file->stream.flags & FD_WRITE))
|
|
{
|
|
DEBUGF("Descriptor is read-only mode\n");
|
|
FILE_ERROR(EBADF, -2);
|
|
}
|
|
|
|
if (length < 0)
|
|
{
|
|
DEBUGF("Length %ld is invalid\n", (long)length);
|
|
FILE_ERROR(EINVAL, -3);
|
|
}
|
|
|
|
rc = ftruncate_internal(file, length, true);
|
|
if (rc < 0)
|
|
FILE_ERROR(ERRNO, rc * 10 - 4);
|
|
|
|
file_error:
|
|
RELEASE_FILESTR(READER, file);
|
|
return rc;
|
|
}
|
|
|
|
/* synchronize changes to a file */
|
|
int fsync(int fildes)
|
|
{
|
|
DEBUGF("fsync(fd=%d)\n", fildes);
|
|
|
|
struct filestr_desc * const file = GET_FILESTR(WRITER, fildes);
|
|
if (!file)
|
|
FILE_ERROR_RETURN(ERRNO, -1);
|
|
|
|
int rc;
|
|
|
|
if (!(file->stream.flags & FD_WRITE))
|
|
{
|
|
DEBUGF("Descriptor is read-only mode\n");
|
|
FILE_ERROR(EINVAL, -2);
|
|
}
|
|
|
|
rc = fsync_internal(file);
|
|
if (rc < 0)
|
|
FILE_ERROR(ERRNO, rc * 10 - 3);
|
|
|
|
file_error:
|
|
RELEASE_FILESTR(WRITER, file);
|
|
return rc;
|
|
}
|
|
|
|
/* move the read/write file offset */
|
|
off_t lseek(int fildes, off_t offset, int whence)
|
|
{
|
|
DEBUGF("lseek(fd=%d,ofs=%ld,wh=%d)\n", fildes, (long)offset, whence);
|
|
|
|
struct filestr_desc * const file = GET_FILESTR(READER, fildes);
|
|
if (!file)
|
|
FILE_ERROR_RETURN(ERRNO, -1);
|
|
|
|
off_t rc = lseek_internal(file, offset, whence);
|
|
if (rc < 0)
|
|
FILE_ERROR(ERRNO, rc * 10 - 2);
|
|
|
|
file_error:
|
|
RELEASE_FILESTR(READER, file);
|
|
return rc;
|
|
}
|
|
|
|
/* read from a file */
|
|
ssize_t read(int fildes, void *buf, size_t nbyte)
|
|
{
|
|
struct filestr_desc * const file = GET_FILESTR(READER, fildes);
|
|
if (!file)
|
|
FILE_ERROR_RETURN(ERRNO, -1);
|
|
|
|
ssize_t rc;
|
|
|
|
if (file->stream.flags & FD_WRONLY)
|
|
{
|
|
DEBUGF("read(fd=%d,buf=%p,nb=%lu) - "
|
|
"descriptor is write-only mode\n",
|
|
fildes, buf, (unsigned long)nbyte);
|
|
FILE_ERROR(EBADF, -2);
|
|
}
|
|
|
|
rc = readwrite(file, buf, nbyte, false);
|
|
if (rc < 0)
|
|
FILE_ERROR(ERRNO, rc * 10 - 3);
|
|
|
|
file_error:
|
|
RELEASE_FILESTR(READER, file);
|
|
return rc;
|
|
}
|
|
|
|
/* write on a file */
|
|
ssize_t write(int fildes, const void *buf, size_t nbyte)
|
|
{
|
|
struct filestr_desc * const file = GET_FILESTR(READER, fildes);
|
|
if (!file)
|
|
FILE_ERROR_RETURN(ERRNO, -1);
|
|
|
|
ssize_t rc;
|
|
|
|
if (!(file->stream.flags & FD_WRITE))
|
|
{
|
|
DEBUGF("write(fd=%d,buf=%p,nb=%lu) - "
|
|
"descriptor is read-only mode\n",
|
|
fildes, buf, (unsigned long)nbyte);
|
|
FILE_ERROR(EBADF, -2);
|
|
}
|
|
|
|
rc = readwrite(file, (void *)buf, nbyte, true);
|
|
if (rc < 0)
|
|
FILE_ERROR(ERRNO, rc * 10 - 3);
|
|
|
|
file_error:
|
|
RELEASE_FILESTR(READER, file);
|
|
return rc;
|
|
}
|
|
|
|
/* remove a file */
|
|
int remove(const char *path)
|
|
{
|
|
DEBUGF("remove(path=\"%s\")\n", path);
|
|
|
|
file_internal_lock_WRITER();
|
|
int rc = remove_stream_internal(path, NULL, FF_FILE);
|
|
file_internal_unlock_WRITER();
|
|
return rc;
|
|
}
|
|
|
|
/* rename a file */
|
|
int rename(const char *old, const char *new)
|
|
{
|
|
DEBUGF("rename(old=\"%s\",new=\"%s\")\n", old, new);
|
|
|
|
int rc, open1rc = -1, open2rc = -1;
|
|
struct filestr_base oldstr, newstr;
|
|
struct path_component_info oldinfo, newinfo;
|
|
|
|
file_internal_lock_WRITER();
|
|
|
|
/* open 'old'; it must exist */
|
|
open1rc = open_stream_internal(old, FF_ANYTYPE | FF_PARENTINFO, &oldstr,
|
|
&oldinfo);
|
|
if (open1rc <= 0)
|
|
{
|
|
DEBUGF("Failed opening old: %d\n", open1rc);
|
|
if (open1rc == 0)
|
|
FILE_ERROR(ENOENT, -1);
|
|
else
|
|
FILE_ERROR(ERRNO, open1rc * 10 - 1);
|
|
}
|
|
|
|
/* if 'old' is a directory then 'new' is also required to be one if 'new'
|
|
is to be overwritten */
|
|
const bool are_dirs = oldinfo.attr & ATTR_DIRECTORY;
|
|
|
|
/* open new (may or may not exist) */
|
|
unsigned int callflags = FF_FILE;
|
|
if (are_dirs)
|
|
{
|
|
/* if 'old' is found while parsing the new directory components then
|
|
'new' contains path prefix that names 'old'; if new and old are in
|
|
the same directory, this tests positive but that is checked later */
|
|
callflags = FF_DIR | FF_CHECKPREFIX;
|
|
newinfo.prefixp = oldstr.infop;
|
|
}
|
|
|
|
open2rc = open_stream_internal(new, callflags | FF_PARENTINFO, &newstr,
|
|
&newinfo);
|
|
if (open2rc < 0)
|
|
{
|
|
DEBUGF("Failed opening new file: %d\n", open2rc);
|
|
FILE_ERROR(ERRNO, open2rc * 10 - 2);
|
|
}
|
|
|
|
#ifdef HAVE_MULTIVOLUME
|
|
if (oldinfo.parentinfo.volume != newinfo.parentinfo.volume)
|
|
{
|
|
DEBUGF("Cross-device link\n");
|
|
FILE_ERROR(EXDEV, -3);
|
|
}
|
|
#endif /* HAVE_MULTIVOLUME */
|
|
|
|
/* if the parent is changing then this is a move, not a simple rename */
|
|
const bool is_move = !fat_file_is_same(&oldinfo.parentinfo.fatfile,
|
|
&newinfo.parentinfo.fatfile);
|
|
/* prefix found and moving? */
|
|
if (is_move && (newinfo.attr & ATTR_PREFIX))
|
|
{
|
|
DEBUGF("New contains prefix that names old\n");
|
|
FILE_ERROR(EINVAL, -4);
|
|
}
|
|
|
|
const char * const oldname = strmemdupa(oldinfo.name, oldinfo.length);
|
|
const char * const newname = strmemdupa(newinfo.name, newinfo.length);
|
|
bool is_overwrite = false;
|
|
|
|
if (open2rc > 0)
|
|
{
|
|
/* new name exists in parent; check if 'old' is overwriting 'new';
|
|
if it's the very same file, then it's just a rename */
|
|
is_overwrite = oldstr.bindp != newstr.bindp;
|
|
|
|
if (is_overwrite)
|
|
{
|
|
if (are_dirs)
|
|
{
|
|
/* the directory to be overwritten must be empty */
|
|
rc = test_dir_empty_internal(&newstr);
|
|
if (rc < 0)
|
|
FILE_ERROR(ERRNO, rc * 10 - 5);
|
|
}
|
|
}
|
|
else if (!strcmp(newname, oldname)) /* case-only is ok */
|
|
{
|
|
DEBUGF("No name change (success)\n");
|
|
rc = 0;
|
|
FILE_ERROR(ERRNO, RC);
|
|
}
|
|
}
|
|
else if (!are_dirs && (newinfo.attr & ATTR_DIRECTORY))
|
|
{
|
|
/* even if new doesn't exist, canonical path type must match
|
|
(ie. a directory path such as "/foo/bar/" when old names a file) */
|
|
DEBUGF("New path is a directory\n");
|
|
FILE_ERROR(EISDIR, -6);
|
|
}
|
|
|
|
/* first, create the new entry so that there's never a time that the
|
|
victim's data has no reference in the directory tree, that is, until
|
|
everything else first succeeds */
|
|
struct file_base_info old_fileinfo = *oldstr.infop;
|
|
rc = fat_rename(&newinfo.parentinfo.fatfile, &oldstr.infop->fatfile,
|
|
newname);
|
|
if (rc < 0)
|
|
{
|
|
DEBUGF("I/O error renaming file: %d\n", rc);
|
|
FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO, rc * 10 - 7);
|
|
}
|
|
|
|
if (is_overwrite)
|
|
{
|
|
/* 'new' would have been assigned its own directory entry and
|
|
succeeded so at this point it is treated like a remove() call
|
|
on the victim which preserves data until the last reference is
|
|
closed */
|
|
rc = remove_stream_internal(NULL, &newstr, callflags);
|
|
if (rc < 0)
|
|
FILE_ERROR(ERRNO, rc * 10 - 8);
|
|
}
|
|
|
|
fileop_onrename_internal(&oldstr, is_move ? &old_fileinfo : NULL,
|
|
&newinfo.parentinfo, newname);
|
|
|
|
file_error:
|
|
/* for now, there is nothing to fail upon closing the old stream */
|
|
if (open1rc >= 0)
|
|
close_stream_internal(&oldstr);
|
|
|
|
/* the 'new' stream could fail to close cleanly because it became
|
|
impossible to remove its data if this was an overwrite operation */
|
|
if (open2rc >= 0)
|
|
{
|
|
int rc2 = close_stream_internal(&newstr);
|
|
if (rc2 < 0 && rc >= 0)
|
|
{
|
|
DEBUGF("Success but failed closing new: %d\n", rc2);
|
|
rc = rc2 * 10 - 9;
|
|
}
|
|
}
|
|
|
|
file_internal_unlock_WRITER();
|
|
return rc;
|
|
}
|
|
|
|
/** Extensions **/
|
|
|
|
int modtime(const char *path, time_t modtime)
|
|
{
|
|
DEBUGF("modtime(path=\"%s\",modtime=%d)\n", path, (int) modtime);
|
|
|
|
int rc, open1rc = -1;
|
|
struct filestr_base pathstr;
|
|
struct path_component_info pathinfo;
|
|
|
|
file_internal_lock_WRITER();
|
|
|
|
open1rc = open_stream_internal(path, FF_ANYTYPE | FF_PARENTINFO,
|
|
&pathstr, &pathinfo);
|
|
if (open1rc <= 0)
|
|
{
|
|
DEBUGF("Failed opening path: %d\n", open1rc);
|
|
if (open1rc == 0)
|
|
FILE_ERROR(ENOENT, -1);
|
|
else
|
|
FILE_ERROR(ERRNO, open1rc * 10 - 1);
|
|
}
|
|
|
|
rc = fat_modtime(&pathinfo.parentinfo.fatfile, pathstr.fatstr.fatfilep,
|
|
modtime);
|
|
if (rc < 0)
|
|
{
|
|
DEBUGF("I/O error during modtime: %d\n", rc);
|
|
FILE_ERROR(ERRNO, rc * 10 - 2);
|
|
}
|
|
|
|
file_error:
|
|
if (open1rc >= 0)
|
|
close_stream_internal(&pathstr);
|
|
file_internal_unlock_WRITER();
|
|
return rc;
|
|
}
|
|
|
|
/* get the binary size of a file (in bytes) */
|
|
off_t filesize(int fildes)
|
|
{
|
|
struct filestr_desc * const file = GET_FILESTR(READER, fildes);
|
|
if (!file)
|
|
FILE_ERROR_RETURN(ERRNO, -1);
|
|
|
|
off_t rc;
|
|
file_size_t size = *file->sizep;
|
|
|
|
if (size > FILE_SIZE_MAX)
|
|
FILE_ERROR(EOVERFLOW, -2);
|
|
|
|
rc = (off_t)size;
|
|
file_error:
|
|
RELEASE_FILESTR(READER, file);
|
|
return rc;
|
|
}
|
|
|
|
/* test if two file descriptors refer to the same file */
|
|
int fsamefile(int fildes1, int fildes2)
|
|
{
|
|
struct filestr_desc * const file1 = GET_FILESTR(WRITER, fildes1);
|
|
if (!file1)
|
|
FILE_ERROR_RETURN(ERRNO, -1);
|
|
|
|
int rc = -2;
|
|
|
|
struct filestr_desc * const file2 = get_filestr(fildes2);
|
|
if (file2)
|
|
rc = file1->stream.bindp == file2->stream.bindp ? 1 : 0;
|
|
|
|
RELEASE_FILESTR(WRITER, file1);
|
|
return rc;
|
|
}
|
|
|
|
/* tell the relationship of path1 to path2 */
|
|
int relate(const char *path1, const char *path2)
|
|
{
|
|
/* this is basically what rename() does but reduced to the relationship
|
|
determination */
|
|
DEBUGF("relate(path1=\"%s\",path2=\"%s\")\n", path1, path2);
|
|
|
|
int rc, open1rc = -1, open2rc = -1;
|
|
struct filestr_base str1, str2;
|
|
struct path_component_info info1, info2;
|
|
|
|
file_internal_lock_WRITER();
|
|
|
|
open1rc = open_stream_internal(path1, FF_ANYTYPE, &str1, &info1);
|
|
if (open1rc <= 0)
|
|
{
|
|
DEBUGF("Failed opening path1: %d\n", open1rc);
|
|
if (open1rc < 0)
|
|
FILE_ERROR(ERRNO, open1rc * 10 - 1);
|
|
else
|
|
FILE_ERROR(ENOENT, -1);
|
|
}
|
|
|
|
info2.prefixp = str1.infop;
|
|
open2rc = open_stream_internal(path2, FF_ANYTYPE | FF_CHECKPREFIX,
|
|
&str2, &info2);
|
|
if (open2rc < 0)
|
|
{
|
|
DEBUGF("Failed opening path2: %d\n", open2rc);
|
|
FILE_ERROR(ERRNO, open2rc * 10 - 2);
|
|
}
|
|
|
|
rc = RELATE_DIFFERENT;
|
|
|
|
if (open2rc > 0)
|
|
{
|
|
if (str1.bindp == str2.bindp)
|
|
rc = RELATE_SAME;
|
|
else if (info2.attr & ATTR_PREFIX)
|
|
rc = RELATE_PREFIX;
|
|
}
|
|
else /* open2rc == 0 */
|
|
{
|
|
/* path1 existing and path2's final part not can only be a prefix or
|
|
different */
|
|
if (info2.attr & ATTR_PREFIX)
|
|
rc = RELATE_PREFIX;
|
|
}
|
|
|
|
file_error:
|
|
if (open1rc >= 0)
|
|
close_stream_internal(&str1);
|
|
|
|
if (open2rc >= 0)
|
|
close_stream_internal(&str2);
|
|
|
|
file_internal_unlock_WRITER();
|
|
return rc;
|
|
}
|
|
|
|
/* test file or directory existence */
|
|
bool file_exists(const char *path)
|
|
{
|
|
file_internal_lock_WRITER();
|
|
bool rc = test_stream_exists_internal(path, FF_ANYTYPE) > 0;
|
|
file_internal_unlock_WRITER();
|
|
return rc;
|
|
}
|