rockbox/firmware/target/hosted/ctru/lib/sys_dir.c
Mauricio Garrido 752467dee4 3ds: Various fixes, optimizations.
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
2026-01-09 20:54:44 -05:00

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 */
}