mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-01-22 01:30:35 -05:00
This commit does the following changes: - Fix mkdir implementation not reporting EEXIST error. - Fix database build feature. - Small speed-up when parsing directories and files. - Fix buffering thread hogging cpu and preventing other threads to run. - Fix sdl plugins not compiling by re-adding ctru specific cflags in sdl.make. Change-Id: I507c0dcc85cdbcc607ab9c9c6d0b42e6a80caa5a
772 lines
20 KiB
C
772 lines
20 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2025 Mauricio G.
|
|
*
|
|
* 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 <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <utime.h>
|
|
#include "file.h"
|
|
#include "debug.h"
|
|
#include "string-extra.h"
|
|
#include "fs_defines.h"
|
|
#include "sys_file.h"
|
|
|
|
/* This file is based on firmware/common/file.c */
|
|
|
|
/* Define LOGF_ENABLE to enable logf output in this file */
|
|
// #define LOGF_ENABLE
|
|
#include "logf.h"
|
|
|
|
/**
|
|
* These functions provide a roughly POSIX-compatible file I/O API.
|
|
* Important: the bufferio I/O library (bfile) used in the 3ds does not work
|
|
* with write-only files due to an internal limitation.
|
|
* So all files will be opened with the read flag by default.
|
|
*/
|
|
|
|
/* 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 */
|
|
u64 *sizep; /* shortcut to file size in fileobj */
|
|
} open_streams[MAX_OPEN_FILES] =
|
|
{
|
|
[0 ... MAX_OPEN_FILES-1] = { .stream = { .cache = NULL, .flags = 0 } }
|
|
};
|
|
|
|
extern FS_Archive sdmcArchive;
|
|
|
|
/* 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.cache != NULL)
|
|
return file;
|
|
|
|
logf("fildes %d: bad file number\n", fildes);
|
|
errno = (file && (file->stream.cache == NULL)) ? 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.cache == NULL)
|
|
{
|
|
*filep = file;
|
|
return fildes;
|
|
}
|
|
}
|
|
|
|
logf("Too many files open\n");
|
|
return -1;
|
|
}
|
|
|
|
/* check for file existence */
|
|
int test_stream_exists_internal(const char *path)
|
|
{
|
|
int rc;
|
|
bool is_dir = false;
|
|
|
|
Handle handle;
|
|
Result res = FSUSER_OpenFile(&handle,
|
|
sdmcArchive,
|
|
fsMakePath(PATH_ASCII, path),
|
|
FS_OPEN_READ,
|
|
0);
|
|
if (R_FAILED(res)) {
|
|
/* not a file, try to open a directory */
|
|
res = FSUSER_OpenDirectory(&handle,
|
|
sdmcArchive,
|
|
fsMakePath(PATH_ASCII, path));
|
|
if (R_FAILED(res)) {
|
|
logf("File does not exist\n");
|
|
FILE_ERROR(ERRNO, -1);
|
|
}
|
|
|
|
is_dir = true;
|
|
}
|
|
|
|
rc = 1;
|
|
file_error:
|
|
if (handle > 0) {
|
|
if (is_dir)
|
|
FSDIR_Close(handle);
|
|
else
|
|
FSFILE_Close(handle);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* set the file pointer */
|
|
static off_t lseek_internal(struct filestr_desc *file, off_t offset,
|
|
int whence)
|
|
{
|
|
off_t rc;
|
|
off_t pos;
|
|
|
|
off_t size = MIN(*file->sizep, FILE_SIZE_MAX);
|
|
off_t file_offset = AtomicLoad(&file->offset);
|
|
|
|
switch (whence)
|
|
{
|
|
case SEEK_SET:
|
|
if (offset < 0 || (off_t)offset > size)
|
|
FILE_ERROR(EINVAL, -1);
|
|
|
|
pos = offset;
|
|
break;
|
|
|
|
case SEEK_CUR:
|
|
if ((offset < 0 && (off_t)-offset > file_offset) ||
|
|
(offset > 0 && (off_t)offset > size - file_offset))
|
|
FILE_ERROR(EINVAL, -1);
|
|
|
|
pos = file_offset + offset;
|
|
break;
|
|
|
|
case SEEK_END:
|
|
if (offset > 0 || (off_t)-offset > size)
|
|
FILE_ERROR(EINVAL, -1);
|
|
|
|
pos = size + offset;
|
|
break;
|
|
|
|
default:
|
|
FILE_ERROR(EINVAL, -1);
|
|
}
|
|
|
|
AtomicSwap(&file->offset, pos);
|
|
|
|
return pos;
|
|
file_error:
|
|
return rc;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
#ifndef LOGF_ENABLE /* wipes out log before you can save it */
|
|
/* DEBUGF("readwrite(%p,%lx,%lu,%s)\n",
|
|
file, (long)buf, (unsigned long)nbyte, write ? "write" : "read"); */
|
|
#endif
|
|
|
|
const file_size_t size = *file->sizep;
|
|
size_t filerem;
|
|
|
|
if (write)
|
|
{
|
|
/* if opened in append mode, move pointer to end */
|
|
if (file->stream.flags & O_APPEND)
|
|
AtomicSwap(&file->offset, MIN(size, FILE_SIZE_MAX));
|
|
|
|
filerem = FILE_SIZE_MAX - AtomicLoad(&file->offset);
|
|
}
|
|
else
|
|
{
|
|
/* limit to maximum possible offset (EOF or FILE_SIZE_MAX) */
|
|
filerem = MIN(size, FILE_SIZE_MAX) - AtomicLoad(&file->offset);
|
|
}
|
|
|
|
if (nbyte > filerem)
|
|
{
|
|
nbyte = filerem;
|
|
if (nbyte > 0)
|
|
{}
|
|
else if (write)
|
|
FILE_ERROR_RETURN(EFBIG, -1); /* would get too large */
|
|
else if (AtomicLoad(&file->offset) >= FILE_SIZE_MAX)
|
|
FILE_ERROR_RETURN(EOVERFLOW, -2); /* can't read here */
|
|
}
|
|
|
|
if (nbyte == 0)
|
|
return 0;
|
|
|
|
int rc = 0;
|
|
int_error_t n_err;
|
|
|
|
if (write) {
|
|
n_err = PageReader_WriteAt(file->stream.cache,
|
|
buf,
|
|
nbyte,
|
|
AtomicLoad(&file->offset),
|
|
file->stream.flags & O_RDWR ? true : false);
|
|
}
|
|
else {
|
|
n_err = PageReader_ReadAt(file->stream.cache,
|
|
buf,
|
|
nbyte,
|
|
AtomicLoad(&file->offset));
|
|
}
|
|
|
|
if ((n_err.err != NULL) && strcmp(n_err.err, "io.EOF")) {
|
|
FILE_ERROR(ERRNO, -3);
|
|
}
|
|
|
|
file_error:;
|
|
#ifdef DEBUG
|
|
if (errno == ENOSPC)
|
|
logf("No space left on device\n");
|
|
#endif
|
|
|
|
size_t done = n_err.n;
|
|
if (done)
|
|
{
|
|
/* error or not, update the file offset and size if anything was
|
|
transferred */
|
|
AtomicAdd(&file->offset, done);
|
|
#ifndef LOGF_ENABLE /* wipes out log before you can save it */
|
|
/* DEBUGF("file offset: %lld\n", file->offset); */
|
|
#endif
|
|
/* adjust file size to length written */
|
|
if (write && AtomicLoad(&file->offset) > size)
|
|
*file->sizep = AtomicLoad(&file->offset);
|
|
|
|
return done;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* initialize the base descriptor */
|
|
static void filestr_base_init(struct filestr_base *stream)
|
|
{
|
|
stream->cache = NULL;
|
|
stream->handle = 0;
|
|
stream->size = 0;
|
|
LightLock_Init(&stream->mtx);
|
|
}
|
|
|
|
int open_internal_inner2(Handle *handle, const char *path, u32 openFlags, u32 attributes)
|
|
{
|
|
int rc;
|
|
Result res = FSUSER_OpenFile(handle,
|
|
sdmcArchive,
|
|
fsMakePath(PATH_ASCII, path),
|
|
openFlags,
|
|
attributes);
|
|
if (R_FAILED(res)) {
|
|
FILE_ERROR(ERRNO, -1);
|
|
}
|
|
|
|
rc = 1;
|
|
file_error:
|
|
return rc;
|
|
}
|
|
|
|
static int open_internal_inner1(const char *path, int oflag)
|
|
{
|
|
int rc;
|
|
struct filestr_desc *file;
|
|
int fildes = alloc_filestr(&file);
|
|
if (fildes < 0)
|
|
FILE_ERROR_RETURN(EMFILE, -1);
|
|
|
|
u32 openFlags = 0, attributes = 0;
|
|
|
|
/* open for reading by default */
|
|
openFlags = FS_OPEN_READ;
|
|
|
|
if (oflag & O_ACCMODE)
|
|
{
|
|
if ((oflag & O_ACCMODE) == O_RDONLY) {
|
|
attributes |= FS_ATTRIBUTE_READ_ONLY;
|
|
}
|
|
if ((oflag & O_ACCMODE) == O_WRONLY) {
|
|
openFlags |= FS_OPEN_WRITE;
|
|
}
|
|
if ((oflag & O_ACCMODE) == O_RDWR) {
|
|
openFlags |= FS_OPEN_WRITE;
|
|
}
|
|
}
|
|
else if (oflag & O_TRUNC)
|
|
{
|
|
/* O_TRUNC requires write mode */
|
|
logf("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" */
|
|
|
|
filestr_base_init(&file->stream);
|
|
rc = open_internal_inner2(&file->stream.handle, path, openFlags, attributes);
|
|
|
|
if (rc > 0) {
|
|
if (oflag & O_EXCL)
|
|
{
|
|
logf("File exists\n");
|
|
FILE_ERROR(EEXIST, -4);
|
|
}
|
|
}
|
|
else if (oflag & O_CREAT)
|
|
{
|
|
/* not found; try to create it */
|
|
openFlags |= FS_OPEN_CREATE;
|
|
rc = open_internal_inner2(&file->stream.handle, path, openFlags, attributes);
|
|
if (rc < 0)
|
|
FILE_ERROR(ERRNO, rc * 10 - 6);
|
|
}
|
|
else
|
|
{
|
|
logf("File not found\n");
|
|
FILE_ERROR(ENOENT, -5);
|
|
}
|
|
|
|
/* truncate file if requested */
|
|
if (oflag & O_TRUNC) {
|
|
Result res = FSFILE_SetSize(file->stream.handle, 0);
|
|
if (R_FAILED(res)) {
|
|
FILE_ERROR(ERRNO, -6);
|
|
}
|
|
}
|
|
|
|
/* we need to set file size here, or else lseek
|
|
will fail if no read or write has been done */
|
|
u64 size = 0;
|
|
Result res = FSFILE_GetSize(file->stream.handle, &size);
|
|
if (R_FAILED(res)) {
|
|
FILE_ERROR(ERRNO, -8);
|
|
}
|
|
|
|
file->stream.cache = NewPageReader(file->stream.handle, defaultPageSize);
|
|
if (file->stream.cache == NULL) {
|
|
FILE_ERROR(ERRNO, -7);
|
|
}
|
|
|
|
file->stream.flags = oflag;
|
|
file->stream.size = size;
|
|
file->sizep = &file->stream.size;
|
|
AtomicSwap(&file->offset, 0);
|
|
|
|
/* we will use file path to implement ctru_fsamefile function */
|
|
strcpy(file->stream.path, path);
|
|
|
|
return fildes;
|
|
|
|
file_error:
|
|
if (fildes >= 0) {
|
|
if (file->stream.cache != NULL) {
|
|
/* FSFILE_Flush(file->stream.handle); */
|
|
PageReader_Free(file->stream.cache);
|
|
file->stream.cache = NULL;
|
|
}
|
|
|
|
FSFILE_Close(file->stream.handle);
|
|
file->stream.handle = 0;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int open_internal_locked(const char *path, int oflag)
|
|
{
|
|
file_internal_lock_WRITER();
|
|
int rc = open_internal_inner1(path, oflag);
|
|
file_internal_unlock_WRITER();
|
|
return rc;
|
|
}
|
|
|
|
int ctru_open(const char *path, int oflag, ...)
|
|
{
|
|
logf("open(path=\"%s\",oflag=%X)\n", path, (unsigned)oflag);
|
|
return open_internal_locked(path, oflag);
|
|
}
|
|
|
|
int ctru_creat(const char *path, mode_t mode)
|
|
{
|
|
logf("creat(path=\"%s\")\n", path);
|
|
return ctru_open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
|
|
}
|
|
|
|
int ctru_close(int fildes)
|
|
{
|
|
logf("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.cache == NULL))
|
|
{
|
|
logf("filedes %d not open\n", fildes);
|
|
FILE_ERROR(EBADF, -2);
|
|
}
|
|
|
|
if (file->stream.cache != NULL) {
|
|
/* FSFILE_Flush(file->stream.handle); */
|
|
PageReader_Free(file->stream.cache);
|
|
file->stream.cache = NULL;
|
|
}
|
|
|
|
FSFILE_Close(file->stream.handle);
|
|
file->stream.handle = 0;
|
|
file->stream.path[0] = '\0';
|
|
|
|
rc = 0;
|
|
file_error:
|
|
file_internal_unlock_WRITER();
|
|
return rc;
|
|
}
|
|
|
|
/* truncate a file to a specified length */
|
|
int ctru_ftruncate(int fildes, off_t length)
|
|
{
|
|
logf("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 & O_RDONLY)
|
|
{
|
|
logf("Descriptor is read-only mode\n");
|
|
FILE_ERROR(EBADF, -2);
|
|
}
|
|
|
|
if (length < 0)
|
|
{
|
|
logf("Length %ld is invalid\n", (long)length);
|
|
FILE_ERROR(EINVAL, -3);
|
|
}
|
|
|
|
Result res = FSFILE_SetSize(file->stream.handle, length);
|
|
if (R_FAILED(res)) {
|
|
FILE_ERROR(ERRNO, -11);
|
|
}
|
|
|
|
*file->sizep = length;
|
|
|
|
rc = 0;
|
|
file_error:
|
|
RELEASE_FILESTR(READER, file);
|
|
return rc;
|
|
}
|
|
|
|
/* synchronize changes to a file */
|
|
int ctru_fsync(int fildes)
|
|
{
|
|
logf("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 & O_RDONLY)
|
|
{
|
|
logf("Descriptor is read-only mode\n");
|
|
FILE_ERROR(EINVAL, -2);
|
|
}
|
|
|
|
/* flush all pending changes to disk */
|
|
Result res = FSFILE_Flush(file->stream.handle);
|
|
if (R_FAILED(res)) {
|
|
FILE_ERROR(ERRNO, -3);
|
|
}
|
|
|
|
rc = 0;
|
|
file_error:
|
|
RELEASE_FILESTR(WRITER, file);
|
|
return rc;
|
|
}
|
|
|
|
/* move the read/write file offset */
|
|
off_t ctru_lseek(int fildes, off_t offset, int whence)
|
|
{
|
|
#ifndef LOGF_ENABLE /* wipes out log before you can save it */
|
|
/* DEBUGF("lseek(fd=%d,ofs=%ld,wh=%d)\n", fildes, (long)offset, whence); */
|
|
#endif
|
|
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 ctru_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 & O_WRONLY)
|
|
{
|
|
logf("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 ctru_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 & O_RDONLY)
|
|
{
|
|
logf("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 ctru_remove(const char *path)
|
|
{
|
|
logf("remove(path=\"%s\")\n", path);
|
|
|
|
int rc;
|
|
|
|
file_internal_lock_WRITER();
|
|
Result res = FSUSER_DeleteFile(sdmcArchive,
|
|
fsMakePath(PATH_ASCII, path));
|
|
if (R_FAILED(res))
|
|
FILE_ERROR(ERRNO, -1);
|
|
|
|
rc = 0;
|
|
file_error:
|
|
file_internal_unlock_WRITER();
|
|
return rc;
|
|
}
|
|
|
|
/* rename a file */
|
|
int ctru_rename(const char *old, const char *new)
|
|
{
|
|
/* note: move by rename does not work in devkitARM toolchain */
|
|
logf("rename(old=\"%s\",new=\"%s\")\n", old, new);
|
|
|
|
int rc;
|
|
|
|
/* if 'old' is a directory then 'new' is also required to be one if 'new'
|
|
is to be overwritten */
|
|
bool are_dirs = false;
|
|
|
|
file_internal_lock_WRITER();
|
|
|
|
if (!strcmp(new, old)) /* case-only is ok */
|
|
{
|
|
logf("No name change (success)\n");
|
|
rc = 0;
|
|
FILE_ERROR(ERRNO, _RC);
|
|
}
|
|
|
|
/* open 'old'; it must exist */
|
|
Handle open1rc;
|
|
Result res = FSUSER_OpenFile(&open1rc,
|
|
sdmcArchive,
|
|
fsMakePath(PATH_ASCII, old),
|
|
FS_OPEN_READ,
|
|
0);
|
|
if (R_FAILED(res)) {
|
|
/* not a file, try to open a directory */
|
|
res = FSUSER_OpenDirectory(&open1rc,
|
|
sdmcArchive,
|
|
fsMakePath(PATH_ASCII, old));
|
|
if (R_FAILED(res)) {
|
|
logf("Failed opening old\n");
|
|
FILE_ERROR(ERRNO, -1);
|
|
}
|
|
|
|
are_dirs = true;
|
|
}
|
|
|
|
if (are_dirs) {
|
|
/* rename directory */
|
|
FSUSER_RenameDirectory(sdmcArchive,
|
|
fsMakePath(PATH_ASCII, old),
|
|
sdmcArchive,
|
|
fsMakePath(PATH_ASCII, new));
|
|
}
|
|
else {
|
|
/* rename file */
|
|
FSUSER_RenameFile(sdmcArchive,
|
|
fsMakePath(PATH_ASCII, old),
|
|
sdmcArchive,
|
|
fsMakePath(PATH_ASCII, new));
|
|
}
|
|
|
|
if (R_FAILED(res)) {
|
|
logf("Rename failed\n");
|
|
FILE_ERROR(ERRNO, -2);
|
|
}
|
|
|
|
rc = 0;
|
|
file_error:
|
|
/* for now, there is nothing to fail upon closing the old stream */
|
|
if (open1rc > 0) {
|
|
if (are_dirs)
|
|
FSDIR_Close(open1rc);
|
|
else
|
|
FSFILE_Close(open1rc);
|
|
}
|
|
|
|
file_internal_unlock_WRITER();
|
|
return rc;
|
|
}
|
|
|
|
/** Extensions **/
|
|
|
|
/* todo: utime does not work in devkitARM toolchain */
|
|
int ctru_modtime(const char *path, time_t modtime)
|
|
{
|
|
struct utimbuf times =
|
|
{
|
|
.actime = modtime,
|
|
.modtime = modtime,
|
|
};
|
|
|
|
return utime(path, ×);
|
|
}
|
|
|
|
/* get the binary size of a file (in bytes) */
|
|
off_t ctru_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 ctru_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 = strcmp(file1->stream.path, file2->stream.path) == 0 ? 1 : 0;
|
|
|
|
RELEASE_FILESTR(WRITER, file1);
|
|
return rc;
|
|
}
|
|
|
|
/* tell the relationship of path1 to path2 */
|
|
int ctru_relate(const char *path1, const char *path2)
|
|
{
|
|
/* FAT32 file system does not support symbolic links,
|
|
therefore, comparing the two full paths should be enough
|
|
to tell relationship */
|
|
logf("relate(path1=\"%s\",path2=\"%s\")\n", path1, path2);
|
|
int rc = RELATE_DIFFERENT;
|
|
if (strcmp(path1, path2) == 0)
|
|
rc = RELATE_SAME;
|
|
return rc;
|
|
}
|
|
|
|
/* test file or directory existence */
|
|
bool ctru_file_exists(const char *path)
|
|
{
|
|
file_internal_lock_WRITER();
|
|
bool rc = test_stream_exists_internal(path) > 0;
|
|
file_internal_unlock_WRITER();
|
|
return rc;
|
|
}
|
|
|
|
/* note: no symbolic links support in devkitARM */
|
|
ssize_t ctru_readlink(const char *path, char *buf, size_t bufsiz)
|
|
{
|
|
return readlink(path, buf, bufsiz);
|
|
}
|