rockbox/firmware/common/dir.c
William Wilgus 9daacabd65 [RESTORED!] Allow mounting of any directory as the root directory.
Provide definitions for the macros:
* RB_ROOT_VOL_HIDDEN(v) to exclude certain items from the root.
* RB_ROOT_CONTENTS to return a string with the name of the
directory to mount in the root.

Defaults are in export/rbpaths.h

It's a bit much for those that don't need the full functionality.
Some conditional define can cut it back a lot to cut out things only
needed if alternate root mounts are required. I'm just not bothering
yet. The basic concept would be applied to all targets to keep file
code from forking too much.

Change-Id: I3b5a14c530ff4b10d97f67636237d96875eb8969
Author: Michael Sevakis
2022-03-03 18:58:07 -05:00

346 lines
8.6 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 DIRFUNCTIONS_DEFINED
#include "config.h"
#include <errno.h>
#include <string.h>
#include "debug.h"
#include "dir.h"
#include "pathfuncs.h"
#include "timefuncs.h"
#include "fileobj_mgr.h"
#include "rb_namespace.h"
/* structure used for open directory streams */
static struct dirstr_desc
{
struct filestr_base stream; /* basic stream info (first!) */
struct ns_scan_info scan; /* directory scan cursor */
struct dirent entry; /* current parsed entry information */
} open_streams[MAX_OPEN_DIRS];
/* 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.flags & (FDO_BUSY|FD_VALID))
return dir;
int errnum;
if (!dir)
{
errnum = EFAULT;
}
else if (dir->stream.flags & FD_NONEXIST)
{
DEBUGF("dir #%d: nonexistant device\n", (int)(dir - open_streams));
errnum = ENXIO;
}
else
{
DEBUGF("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.flags)
return dir;
}
DEBUGF("Too many dirs open\n");
return NULL;
}
/** POSIX interface **/
/* open a directory */
DIR * opendir(const char *dirname)
{
DEBUGF("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);
rc = ns_open_stream(dirname, FF_DIR, &dir->stream, &dir->scan);
if (rc < 0)
{
DEBUGF("Open failed: %d\n", rc);
FILE_ERROR(ERRNO, RC);
}
dirp = (DIR *)dir;
file_error:
file_internal_unlock_WRITER();
return dirp;
}
/* close a directory stream */
int 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);
if (!dir->stream.flags)
{
DEBUGF("dir #%d: dir not open\n", (int)(dir - open_streams));
FILE_ERROR(EBADF, -2);
}
rc = ns_close_stream(&dir->stream);
if (rc < 0)
FILE_ERROR(ERRNO, rc * 10 - 3);
file_error:
file_internal_unlock_WRITER();
return rc;
}
/* read a directory */
struct dirent * readdir(DIR *dirp)
{
struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp);
if (!dir)
FILE_ERROR_RETURN(ERRNO, NULL);
struct dirent *res = NULL;
int rc = ns_readdir_dirent(&dir->stream, &dir->scan, &dir->entry);
if (rc > 0)
res = &dir->entry;
else if (rc < 0)
FILE_ERROR(EIO, RC);
file_error:
RELEASE_DIRSTR(READER, dir);
if (rc > 1)
iso_decode_d_name(res->d_name);
return res;
}
#if 0 /* not included now but probably should be */
/* read a directory (reentrant) */
int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result)
{
if (!result)
FILE_ERROR_RETURN(EFAULT, -2);
*result = NULL;
if (!entry)
FILE_ERROR_RETURN(EFAULT, -3);
struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp);
if (!dir)
FILE_ERROR_RETURN(ERRNO, -1);
int rc = ns_readdir_dirent(&dir->stream, &dir->scan, entry);
if (rc < 0)
FILE_ERROR(EIO, rc * 10 - 4);
file_error:
RELEASE_DIRSTR(READER, dir);
if (rc > 0)
{
if (rc > 1)
iso_decode_d_name(entry->d_name);
*result = entry;
rc = 0;
}
return rc;
}
/* reset the position of a directory stream to the beginning of a directory */
void rewinddir(DIR *dirp)
{
struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp);
if (!dir)
FILE_ERROR_RETURN(ERRNO);
ns_dirscan_rewind(&dir->scan);
RELEASE_DIRSTR(READER, dir);
}
#endif /* 0 */
/* make a directory */
int mkdir(const char *path)
{
DEBUGF("mkdir(path=\"%s\")\n", path);
int rc;
file_internal_lock_WRITER();
struct filestr_base stream;
struct path_component_info compinfo;
rc = open_stream_internal(path, FF_DIR | FF_PARENTINFO, &stream,
&compinfo);
if (rc < 0)
{
DEBUGF("Can't open parent dir or path is not a directory\n");
FILE_ERROR(ERRNO, rc * 10 - 1);
}
else if (rc > 0)
{
DEBUGF("File exists\n");
FILE_ERROR(EEXIST, -2);
}
rc = create_stream_internal(&compinfo.parentinfo, compinfo.name,
compinfo.length, ATTR_NEW_DIRECTORY,
FO_DIRECTORY, &stream);
if (rc < 0)
FILE_ERROR(ERRNO, rc * 10 - 3);
rc = 0;
file_error:
close_stream_internal(&stream);
file_internal_unlock_WRITER();
return rc;
}
/* remove a directory */
int rmdir(const char *name)
{
DEBUGF("rmdir(name=\"%s\")\n", name);
if (name)
{
/* path may not end with "." */
const char *basename;
size_t len = path_basename(name, &basename);
if (basename[0] == '.' && len == 1)
{
DEBUGF("Invalid path; last component is \".\"\n");
FILE_ERROR_RETURN(EINVAL, -9);
}
}
file_internal_lock_WRITER();
int rc = remove_stream_internal(name, NULL, FF_DIR);
file_internal_unlock_WRITER();
return rc;
}
/** Extended interface **/
/* return if two directory streams refer to the same directory */
int 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 = dir1->stream.bindp == dir2->stream.bindp ? 1 : 0;
RELEASE_DIRSTR(WRITER, dir1);
return rc;
}
/* test directory existence (returns 'false' if a file) */
bool dir_exists(const char *dirname)
{
file_internal_lock_WRITER();
bool rc = test_stream_exists_internal(dirname, FF_DIR) > 0;
file_internal_unlock_WRITER();
return rc;
}
/* 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 };
}