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
426 lines
11 KiB
C
426 lines
11 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 DIRFUNCTIONS_DEFINED
|
|
#include "config.h"
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include "debug.h"
|
|
#include "dir.h"
|
|
#include "pathfuncs.h"
|
|
#include "timefuncs.h"
|
|
#include "system.h"
|
|
#include "fs_defines.h"
|
|
#include "sys_file.h"
|
|
|
|
#include <3ds/archive.h>
|
|
#include <3ds/util/utf.h>
|
|
|
|
/* This file is based on firmware/common/dir.c */
|
|
|
|
/* Define LOGF_ENABLE to enable logf output in this file */
|
|
// #define LOGF_ENABLE
|
|
#include "logf.h"
|
|
|
|
/* structure used for open directory streams */
|
|
static struct dirstr_desc
|
|
{
|
|
struct filestr_base stream; /* basic stream info (first!) */
|
|
struct dirent entry; /* current parsed entry information */
|
|
} open_streams[MAX_OPEN_DIRS] =
|
|
{
|
|
[0 ... MAX_OPEN_FILES-1] = { .stream = { .cache = NULL } }
|
|
};
|
|
|
|
extern FS_Archive sdmcArchive;
|
|
|
|
/* check and return a struct dirstr_desc* from a DIR* */
|
|
static struct dirstr_desc * get_dirstr(DIR *dirp)
|
|
{
|
|
struct dirstr_desc *dir = (struct dirstr_desc *)dirp;
|
|
|
|
if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS))
|
|
dir = NULL;
|
|
else if (dir->stream.cache != NULL)
|
|
return dir;
|
|
|
|
int errnum;
|
|
|
|
if (!dir)
|
|
{
|
|
errnum = EFAULT;
|
|
}
|
|
else
|
|
{
|
|
logf("dir #%d: dir not open\n", (int)(dir - open_streams));
|
|
errnum = EBADF;
|
|
}
|
|
|
|
errno = errnum;
|
|
return NULL;
|
|
}
|
|
|
|
#define GET_DIRSTR(type, dirp) \
|
|
({ \
|
|
file_internal_lock_##type(); \
|
|
struct dirstr_desc *_dir = get_dirstr(dirp); \
|
|
if (_dir) \
|
|
FILESTR_LOCK(type, &_dir->stream); \
|
|
else { \
|
|
file_internal_unlock_##type(); \
|
|
} \
|
|
_dir; \
|
|
})
|
|
|
|
/* release the lock on the dirstr_desc* */
|
|
#define RELEASE_DIRSTR(type, dir) \
|
|
({ \
|
|
FILESTR_UNLOCK(type, &(dir)->stream); \
|
|
file_internal_unlock_##type(); \
|
|
})
|
|
|
|
|
|
/* find a free dir stream descriptor */
|
|
static struct dirstr_desc * alloc_dirstr(void)
|
|
{
|
|
for (unsigned int dd = 0; dd < MAX_OPEN_DIRS; dd++)
|
|
{
|
|
struct dirstr_desc *dir = &open_streams[dd];
|
|
if (dir->stream.cache == NULL)
|
|
return dir;
|
|
}
|
|
|
|
logf("Too many dirs open\n");
|
|
return NULL;
|
|
}
|
|
|
|
u32 fs_error(void) {
|
|
u32 err;
|
|
FSUSER_GetSdmcFatfsError(&err);
|
|
return err;
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/** POSIX interface **/
|
|
|
|
/* open a directory */
|
|
DIR * ctru_opendir(const char *dirname)
|
|
{
|
|
logf("opendir(dirname=\"%s\")\n", dirname);
|
|
|
|
DIR *dirp = NULL;
|
|
file_internal_lock_WRITER();
|
|
|
|
int rc;
|
|
|
|
struct dirstr_desc * const dir = alloc_dirstr();
|
|
if (!dir)
|
|
FILE_ERROR(EMFILE, _RC);
|
|
|
|
filestr_base_init(&dir->stream);
|
|
Result res = FSUSER_OpenDirectory(&dir->stream.handle,
|
|
sdmcArchive,
|
|
fsMakePath(PATH_ASCII, dirname));
|
|
if (R_FAILED(res)) {
|
|
logf("Open failed: %lld\n", fs_error());
|
|
FILE_ERROR(EMFILE, -1);
|
|
}
|
|
|
|
dir->stream.cache = NewPageReaderDirectory(dir->stream.handle, defaultPageSize);
|
|
if (dir->stream.cache == NULL) {
|
|
FILE_ERROR(ERRNO, -2);
|
|
}
|
|
|
|
dir->stream.size = 0;
|
|
dir->stream.flags = 0;
|
|
|
|
/* we will use file path to implement ctru_samedir function */
|
|
strcpy(dir->stream.path, dirname);
|
|
|
|
dirp = (DIR *)dir;
|
|
file_error:
|
|
if (dir->stream.handle != 0) {
|
|
FSDIR_Close(dir->stream.handle);
|
|
dir->stream.handle = 0;
|
|
}
|
|
file_internal_unlock_WRITER();
|
|
return dirp;
|
|
}
|
|
|
|
/* close a directory stream */
|
|
int ctru_closedir(DIR *dirp)
|
|
{
|
|
int rc;
|
|
|
|
file_internal_lock_WRITER();
|
|
|
|
/* needs to work even if marked "nonexistant" */
|
|
struct dirstr_desc * const dir = (struct dirstr_desc *)dirp;
|
|
if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS))
|
|
FILE_ERROR(EFAULT, -1);
|
|
|
|
logf("closedir(dirname=\"%s\")\n", dir->stream.path);
|
|
|
|
if (dir->stream.cache == NULL)
|
|
{
|
|
logf("dir #%d: dir not open\n", (int)(dir - open_streams));
|
|
FILE_ERROR(EBADF, -2);
|
|
}
|
|
|
|
PageReader_Free(dir->stream.cache);
|
|
dir->stream.cache = NULL;
|
|
|
|
Result res = FSDIR_Close(dir->stream.handle);
|
|
if (R_FAILED(res))
|
|
FILE_ERROR(ERRNO, -3);
|
|
|
|
dir->stream.handle = 0;
|
|
dir->stream.path[0] = '\0';
|
|
|
|
rc = 0;
|
|
file_error:
|
|
file_internal_unlock_WRITER();
|
|
return rc;
|
|
}
|
|
|
|
void dirstr_entry_init(FS_DirectoryEntry *dirEntry, struct dirent *entry)
|
|
{
|
|
/* clear */
|
|
memset(entry, 0, sizeof(struct dirent));
|
|
|
|
/* attributes */
|
|
if (dirEntry->attributes & FS_ATTRIBUTE_DIRECTORY)
|
|
entry->info.attr |= ATTR_DIRECTORY;
|
|
if (dirEntry->attributes & FS_ATTRIBUTE_HIDDEN)
|
|
entry->info.attr |= ATTR_HIDDEN;
|
|
if (dirEntry->attributes & FS_ATTRIBUTE_ARCHIVE)
|
|
entry->info.attr |= ATTR_ARCHIVE;
|
|
if (dirEntry->attributes & FS_ATTRIBUTE_READ_ONLY)
|
|
entry->info.attr |= ATTR_READ_ONLY;
|
|
|
|
/* size */
|
|
entry->info.size = dirEntry->fileSize;
|
|
|
|
/* name */
|
|
uint8_t d_name[0xA0 + 1];
|
|
memset(d_name, '\0', 0xA0);
|
|
utf16_to_utf8(d_name, (uint16_t *) &dirEntry->name, 0xA0);
|
|
memcpy(entry->d_name, d_name, 0xA0);
|
|
}
|
|
|
|
/* read a directory */
|
|
struct dirent * ctru_readdir(DIR *dirp)
|
|
{
|
|
struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp);
|
|
if (!dir)
|
|
FILE_ERROR_RETURN(ERRNO, NULL);
|
|
|
|
int rc;
|
|
struct dirent *res = NULL;
|
|
|
|
logf("readdir(dirname=\"%s\")\n", dir->stream.path);
|
|
|
|
FS_DirectoryEntry dirEntry;
|
|
int_error_t n_err = PageReader_ReadDir(dir->stream.cache, &dirEntry);
|
|
if (n_err.n < 0)
|
|
FILE_ERROR(EIO, _RC);
|
|
|
|
if (n_err.n == 0) {
|
|
/* directory end. return NULL value, no errno */
|
|
res = NULL;
|
|
goto file_error;
|
|
}
|
|
|
|
res = &dir->entry;
|
|
dirstr_entry_init(&dirEntry, res);
|
|
|
|
/* time */
|
|
char full_path[MAX_PATH+1];
|
|
|
|
if (!strcmp(PATH_ROOTSTR, dir->stream.path))
|
|
snprintf(full_path, MAX_PATH, "%s%s", dir->stream.path, res->d_name);
|
|
else
|
|
snprintf(full_path, MAX_PATH, "%s/%s", dir->stream.path, res->d_name);
|
|
|
|
u64 mtime;
|
|
archive_getmtime(full_path, &mtime);
|
|
|
|
/* DEBUGF("archive_getmtime(%s): %lld\n", full_path, mtime); */
|
|
|
|
uint16_t dosdate, dostime;
|
|
dostime_localtime(mtime, &dosdate, &dostime);
|
|
res->info.wrtdate = dosdate;
|
|
res->info.wrttime = dostime;
|
|
|
|
file_error:
|
|
RELEASE_DIRSTR(READER, dir);
|
|
return res;
|
|
}
|
|
|
|
/* libctru result.h description values appear to be missing
|
|
FSUSER_CreateDirectory return values */
|
|
|
|
enum
|
|
{
|
|
RD_DIRECTORY_ALREADY_EXISTS = 0xBE,
|
|
RD_MISSING_PARENT_DIRECTORY = 0x78,
|
|
};
|
|
|
|
/* make a directory */
|
|
int ctru_mkdir(const char *path)
|
|
{
|
|
logf("mkdir(path=\"%s\")\n", path);
|
|
|
|
int rc;
|
|
|
|
file_internal_lock_WRITER();
|
|
|
|
Result res = FSUSER_CreateDirectory(sdmcArchive,
|
|
fsMakePath(PATH_ASCII, path),
|
|
0);
|
|
if (R_FAILED(res)) {
|
|
if (R_DESCRIPTION(res) == RD_DIRECTORY_ALREADY_EXISTS)
|
|
FILE_ERROR(ERRNO, EEXIST);
|
|
if (R_DESCRIPTION(res) == RD_MISSING_PARENT_DIRECTORY)
|
|
FILE_ERROR(ERRNO, ENOENT);
|
|
/* Unknown error */
|
|
FILE_ERROR(ERRNO, -1);
|
|
}
|
|
|
|
rc = 0;
|
|
file_error:
|
|
file_internal_unlock_WRITER();
|
|
return rc;
|
|
}
|
|
|
|
/* remove a directory */
|
|
int ctru_rmdir(const char *name)
|
|
{
|
|
logf("rmdir(name=\"%s\")\n", name);
|
|
|
|
int rc;
|
|
|
|
if (name)
|
|
{
|
|
/* path may not end with "." */
|
|
const char *basename;
|
|
size_t len = path_basename(name, &basename);
|
|
if (basename[0] == '.' && len == 1)
|
|
{
|
|
logf("Invalid path; last component is \".\"\n");
|
|
FILE_ERROR_RETURN(EINVAL, -9);
|
|
}
|
|
}
|
|
|
|
file_internal_lock_WRITER();
|
|
Result res = FSUSER_DeleteDirectory(sdmcArchive,
|
|
fsMakePath(PATH_ASCII, name));
|
|
if (R_FAILED(res))
|
|
FILE_ERROR(ERRNO, -1);
|
|
|
|
rc = 0;
|
|
file_error:
|
|
file_internal_unlock_WRITER();
|
|
return rc;
|
|
}
|
|
|
|
|
|
/** Extended interface **/
|
|
|
|
/* return if two directory streams refer to the same directory */
|
|
int ctru_samedir(DIR *dirp1, DIR *dirp2)
|
|
{
|
|
struct dirstr_desc * const dir1 = GET_DIRSTR(WRITER, dirp1);
|
|
if (!dir1)
|
|
FILE_ERROR_RETURN(ERRNO, -1);
|
|
|
|
int rc = -2;
|
|
|
|
struct dirstr_desc * const dir2 = get_dirstr(dirp2);
|
|
if (dir2) {
|
|
rc = strcmp(dir1->stream.path, dir2->stream.path) == 0 ? 1 : 0;
|
|
}
|
|
|
|
RELEASE_DIRSTR(WRITER, dir1);
|
|
return rc;
|
|
}
|
|
|
|
/* test directory existence (returns 'false' if a file) */
|
|
bool ctru_dir_exists(const char *dirname)
|
|
{
|
|
file_internal_lock_WRITER();
|
|
|
|
int rc;
|
|
|
|
Handle handle;
|
|
Result res = FSUSER_OpenDirectory(&handle,
|
|
sdmcArchive,
|
|
fsMakePath(PATH_ASCII, dirname));
|
|
if (R_FAILED(res)) {
|
|
logf("Directory not found: %ld\n", fs_error());
|
|
FILE_ERROR(EMFILE, -1);
|
|
}
|
|
|
|
rc = 0;
|
|
file_error:
|
|
if (rc == 0) {
|
|
FSDIR_Close(handle);
|
|
}
|
|
file_internal_unlock_WRITER();
|
|
return rc == 0 ? true : false;
|
|
}
|
|
|
|
/* get the portable info from the native entry */
|
|
struct dirinfo dir_get_info(DIR *dirp, struct dirent *entry)
|
|
{
|
|
int rc;
|
|
if (!dirp || !entry)
|
|
FILE_ERROR(EFAULT, _RC);
|
|
|
|
if (entry->d_name[0] == '\0')
|
|
FILE_ERROR(ENOENT, _RC);
|
|
|
|
if ((file_size_t)entry->info.size > FILE_SIZE_MAX)
|
|
FILE_ERROR(EOVERFLOW, _RC);
|
|
|
|
return (struct dirinfo)
|
|
{
|
|
.attribute = entry->info.attr,
|
|
.size = entry->info.size,
|
|
.mtime = dostime_mktime(entry->info.wrtdate, entry->info.wrttime),
|
|
};
|
|
|
|
file_error:
|
|
return (struct dirinfo){ .attribute = 0 };
|
|
}
|
|
|
|
const char* ctru_root_realpath(void)
|
|
{
|
|
/* Native only, for APP and SIM see respective filesystem-.c files */
|
|
return PATH_ROOTSTR; /* rb_namespace.c */
|
|
}
|