mirror of
https://github.com/hax4dazy/TinWoo.git
synced 2025-02-09 19:25:05 +01:00
1247 lines
41 KiB
C
1247 lines
41 KiB
C
/*
|
|
* ntfs_dev.c
|
|
*
|
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
|
* Copyright (c) 2020-2021, Rhys Koedijk.
|
|
*
|
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
|
*
|
|
* Based on work from libntfs-wii (https://github.com/rhyskoedijk/libntfs-wii). Also loosely based on fs_dev.c from libnx, et al.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "../usbhsfs_manager.h"
|
|
#include "../usbhsfs_mount.h"
|
|
|
|
/* Helper macros. */
|
|
|
|
#define ntfs_end goto end
|
|
#define ntfs_ended_with_error (_errno != 0)
|
|
#define ntfs_set_error(x) r->_errno = _errno = (x)
|
|
#define ntfs_set_error_and_exit(x) \
|
|
do { \
|
|
ntfs_set_error((x)); \
|
|
ntfs_end; \
|
|
} while(0)
|
|
|
|
#define ntfs_declare_error_state int _errno = 0
|
|
#define ntfs_declare_file_state ntfs_file_state *file = (ntfs_file_state*)fd
|
|
#define ntfs_declare_dir_state ntfs_dir_state *dir = (ntfs_dir_state*)dirState->dirStruct
|
|
#define ntfs_declare_fs_ctx UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx = (UsbHsFsDriveLogicalUnitFileSystemContext*)r->deviceData
|
|
#define ntfs_declare_lun_ctx UsbHsFsDriveLogicalUnitContext *lun_ctx = (UsbHsFsDriveLogicalUnitContext*)fs_ctx->lun_ctx
|
|
#define ntfs_declare_drive_ctx UsbHsFsDriveContext *drive_ctx = (UsbHsFsDriveContext*)lun_ctx->drive_ctx
|
|
#define ntfs_declare_vol_state ntfs_vd *vd = fs_ctx->ntfs
|
|
|
|
#define ntfs_lock_drive_ctx ntfs_declare_fs_ctx; \
|
|
ntfs_declare_lun_ctx; \
|
|
ntfs_declare_drive_ctx; \
|
|
bool drive_ctx_valid = usbHsFsManagerIsDriveContextPointerValid(drive_ctx); \
|
|
if (!drive_ctx_valid) ntfs_set_error_and_exit(ENODEV)
|
|
|
|
#define ntfs_unlock_drive_ctx if (drive_ctx_valid) mutexUnlock(&(drive_ctx->mutex))
|
|
|
|
#define ntfs_return(x) return (ntfs_ended_with_error ? -1 : (x))
|
|
#define ntfs_return_ptr(x) return (ntfs_ended_with_error ? NULL : (x))
|
|
#define ntfs_return_bool return (ntfs_ended_with_error ? false : true)
|
|
|
|
/* Type definitions. */
|
|
|
|
/// NTFS file state.
|
|
typedef struct _ntfs_file_state {
|
|
ntfs_vd *vd; ///< File volume descriptor.
|
|
ntfs_inode *ni; ///< File node descriptor.
|
|
ntfs_attr *data; ///< File data attribute descriptor.
|
|
int flags; ///< File open flags.
|
|
bool read; ///< True if allowed to read from file.
|
|
bool write; ///< True if allowed to write to file.
|
|
bool append; ///< True if allowed to append to file.
|
|
bool compressed; ///< True if file data is compressed.
|
|
bool encrypted; ///< True if file data is encryted.
|
|
off_t pos; ///< Current position within the file (in bytes).
|
|
u64 len; ///< Total file length (in bytes).
|
|
} ntfs_file_state;
|
|
|
|
/// NTFS directory entry.
|
|
typedef struct _ntfs_dir_entry {
|
|
u64 mref; ///< Entry record number.
|
|
char *name; ///< Entry name.
|
|
struct _ntfs_dir_entry *next; ///< Next entry in the directory.
|
|
} ntfs_dir_entry;
|
|
|
|
/// NTFS directory state.
|
|
typedef struct _ntfs_dir_state {
|
|
ntfs_vd *vd; ///< Directory volume descriptor.
|
|
ntfs_inode *ni; ///< Directory node descriptor.
|
|
s64 pos; ///< Current position in the directory.
|
|
ntfs_dir_entry *first; ///< The first entry in the directory.
|
|
ntfs_dir_entry *current; ///< The current entry in the directory.
|
|
} ntfs_dir_state;
|
|
|
|
/* Function prototypes. */
|
|
|
|
static int ntfsdev_open(struct _reent *r, void *fd, const char *path, int flags, int mode);
|
|
static int ntfsdev_close(struct _reent *r, void *fd);
|
|
static ssize_t ntfsdev_write(struct _reent *r, void *fd, const char *ptr, size_t len);
|
|
static ssize_t ntfsdev_read(struct _reent *r, void *fd, char *ptr, size_t len);
|
|
static off_t ntfsdev_seek(struct _reent *r, void *fd, off_t pos, int dir);
|
|
static int ntfsdev_fstat(struct _reent *r, void *fd, struct stat *st);
|
|
static int ntfsdev_stat(struct _reent *r, const char *file, struct stat *st);
|
|
static int ntfsdev_link(struct _reent *r, const char *existing, const char *newLink);
|
|
static int ntfsdev_unlink(struct _reent *r, const char *name);
|
|
static int ntfsdev_chdir(struct _reent *r, const char *name);
|
|
static int ntfsdev_rename(struct _reent *r, const char *oldName, const char *newName);
|
|
static int ntfsdev_mkdir(struct _reent *r, const char *path, int mode);
|
|
static DIR_ITER* ntfsdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path);
|
|
static int ntfsdev_dirreset(struct _reent *r, DIR_ITER *dirState);
|
|
static int ntfsdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat);
|
|
static int ntfsdev_dirclose(struct _reent *r, DIR_ITER *dirState);
|
|
static int ntfsdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf);
|
|
static int ntfsdev_ftruncate(struct _reent *r, void *fd, off_t len);
|
|
static int ntfsdev_fsync(struct _reent *r, void *fd);
|
|
static int ntfsdev_chmod(struct _reent *r, const char *path, mode_t mode);
|
|
static int ntfsdev_fchmod(struct _reent *r, void *fd, mode_t mode);
|
|
static int ntfsdev_rmdir(struct _reent *r, const char *name);
|
|
static int ntfsdev_utimes(struct _reent *r, const char *filename, const struct timeval times[2]);
|
|
|
|
static bool ntfsdev_fixpath(struct _reent *r, const char *path, UsbHsFsDriveLogicalUnitFileSystemContext **fs_ctx, char *outpath);
|
|
|
|
static void ntfsdev_fill_stat(ntfs_vd *vd, ntfs_inode *ni, struct stat *st);
|
|
|
|
static int ntfsdev_dirnext_filldir(void *dirent, const ntfschar *name, const int name_len, const int name_type, const s64 pos, const MFT_REF mref, const unsigned dt_type);
|
|
|
|
/* Global variables. */
|
|
|
|
static const devoptab_t ntfsdev_devoptab = {
|
|
.name = NULL,
|
|
.structSize = sizeof(ntfs_file_state),
|
|
.open_r = ntfsdev_open,
|
|
.close_r = ntfsdev_close,
|
|
.write_r = ntfsdev_write,
|
|
.read_r = ntfsdev_read,
|
|
.seek_r = ntfsdev_seek,
|
|
.fstat_r = ntfsdev_fstat,
|
|
.stat_r = ntfsdev_stat,
|
|
.link_r = ntfsdev_link,
|
|
.unlink_r = ntfsdev_unlink,
|
|
.chdir_r = ntfsdev_chdir,
|
|
.rename_r = ntfsdev_rename,
|
|
.mkdir_r = ntfsdev_mkdir,
|
|
.dirStateSize = sizeof(ntfs_dir_state),
|
|
.diropen_r = ntfsdev_diropen,
|
|
.dirreset_r = ntfsdev_dirreset,
|
|
.dirnext_r = ntfsdev_dirnext,
|
|
.dirclose_r = ntfsdev_dirclose,
|
|
.statvfs_r = ntfsdev_statvfs,
|
|
.ftruncate_r = ntfsdev_ftruncate,
|
|
.fsync_r = ntfsdev_fsync,
|
|
.deviceData = NULL,
|
|
.chmod_r = ntfsdev_chmod, ///< Not implemented.
|
|
.fchmod_r = ntfsdev_fchmod, ///< Not implemented.
|
|
.rmdir_r = ntfsdev_rmdir,
|
|
.lstat_r = ntfsdev_stat,
|
|
.utimes_r = ntfsdev_utimes
|
|
};
|
|
|
|
const devoptab_t *ntfsdev_get_devoptab()
|
|
{
|
|
return &ntfsdev_devoptab;
|
|
}
|
|
|
|
static int ntfsdev_open(struct _reent *r, void *fd, const char *path, int flags, int mode)
|
|
{
|
|
(void)mode;
|
|
|
|
ntfs_declare_error_state;
|
|
ntfs_declare_file_state;
|
|
ntfs_lock_drive_ctx;
|
|
ntfs_declare_vol_state;
|
|
|
|
/* Sanity check. */
|
|
if (!file) ntfs_set_error_and_exit(EINVAL);
|
|
|
|
/* Fix input path. */
|
|
if (!ntfsdev_fixpath(r, path, &fs_ctx, NULL)) ntfs_end;
|
|
|
|
/* Setup file state. */
|
|
memset(file, 0, sizeof(ntfs_file_state));
|
|
file->vd = vd;
|
|
|
|
/* Check access mode. */
|
|
switch(flags & O_ACCMODE)
|
|
{
|
|
case O_RDONLY: /* Read-only. Don't allow append flag. */
|
|
if (flags & O_APPEND) ntfs_set_error_and_exit(EINVAL);
|
|
file->read = true;
|
|
file->write = false;
|
|
file->append = false;
|
|
break;
|
|
case O_WRONLY: /* Write-only. */
|
|
file->read = false;
|
|
file->write = true;
|
|
file->append = (flags & O_APPEND);
|
|
break;
|
|
case O_RDWR: /* Read and write. */
|
|
file->read = true;
|
|
file->write = true;
|
|
file->append = (flags & O_APPEND);
|
|
break;
|
|
default: /* Invalid option. */
|
|
ntfs_set_error_and_exit(EINVAL);
|
|
}
|
|
|
|
USBHSFS_LOG_MSG("Opening file \"%s\" (\"%s\") with flags 0x%X.", path, __usbhsfs_dev_path_buf, flags);
|
|
|
|
/* Open file. */
|
|
file->ni = ntfs_inode_open_from_path(file->vd, __usbhsfs_dev_path_buf);
|
|
if (file->ni)
|
|
{
|
|
/* The file already exists, check flags. */
|
|
/* Create + exclusive when the file already exists should throw "file exists" error. */
|
|
if ((flags & O_CREAT) && (flags & O_EXCL)) ntfs_set_error_and_exit(EEXIST);
|
|
|
|
/* Ensure that this file is not actually a directory */
|
|
if (file->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) ntfs_set_error_and_exit(EISDIR);
|
|
} else {
|
|
/* The file doesn't exist yet. Check flags. */
|
|
/* Were we suppose to create this file? */
|
|
if (flags & O_CREAT)
|
|
{
|
|
/* Create the file */
|
|
USBHSFS_LOG_MSG("Creating \"%s\".", __usbhsfs_dev_path_buf);
|
|
file->ni = ntfs_inode_create(file->vd, __usbhsfs_dev_path_buf, S_IFREG, NULL);
|
|
if (!file->ni) ntfs_set_error_and_exit(errno);
|
|
} else {
|
|
/* Can't open file, does not exist. */
|
|
ntfs_set_error_and_exit(ENOENT);
|
|
}
|
|
}
|
|
|
|
/* Open file data attribute. */
|
|
file->data = ntfs_attr_open(file->ni, AT_DATA, AT_UNNAMED, 0);
|
|
if (!file->data) ntfs_set_error_and_exit(errno);
|
|
|
|
/* Determine if this file is compressed and/or encrypted. */
|
|
file->compressed = (NAttrCompressed(file->data) || (file->ni->flags & FILE_ATTR_COMPRESSED));
|
|
file->encrypted = (NAttrEncrypted(file->data) || (file->ni->flags & FILE_ATTR_ENCRYPTED));
|
|
|
|
/* We cannot read/write encrypted files. */
|
|
if (file->encrypted) ntfs_set_error_and_exit(EACCES);
|
|
|
|
/* Check if we're trying to write to a read-only file. */
|
|
if ((file->ni->flags & FILE_ATTR_READONLY) && file->write && !vd->ignore_read_only_attr) ntfs_set_error_and_exit(EROFS);
|
|
|
|
/* Truncate the file if requested. */
|
|
if ((flags & O_TRUNC) && file->write)
|
|
{
|
|
USBHSFS_LOG_MSG("Truncating \"%s\".", __usbhsfs_dev_path_buf);
|
|
|
|
if (!ntfs_attr_truncate(file->data, 0))
|
|
{
|
|
/* Mark the file as dirty. */
|
|
NInoSetDirty(file->ni);
|
|
|
|
/* Mark the file for archiving. */
|
|
file->ni->flags |= FILE_ATTR_ARCHIVE;
|
|
|
|
/* Update file last access and modify times. */
|
|
ntfs_inode_update_times_filtered(file->vd, file->ni, NTFS_UPDATE_AMCTIME);
|
|
} else {
|
|
ntfs_set_error_and_exit(errno);
|
|
}
|
|
}
|
|
|
|
/* Set file current position and length. */
|
|
file->pos = 0;
|
|
file->len = file->data->data_size;
|
|
|
|
/* Update last access time. */
|
|
ntfs_inode_update_times_filtered(file->vd, file->ni, NTFS_UPDATE_ATIME);
|
|
|
|
end:
|
|
/* Clean up if something went wrong. */
|
|
if (ntfs_ended_with_error && file)
|
|
{
|
|
if (file->data) ntfs_attr_close(file->data);
|
|
|
|
if (file->ni) ntfs_inode_close(file->ni);
|
|
|
|
memset(file, 0, sizeof(ntfs_file_state));
|
|
}
|
|
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return(0);
|
|
}
|
|
|
|
static int ntfsdev_close(struct _reent *r, void *fd)
|
|
{
|
|
ntfs_declare_error_state;
|
|
ntfs_declare_file_state;
|
|
ntfs_lock_drive_ctx;
|
|
|
|
/* Sanity check. */
|
|
if (!file || !file->ni || !file->data) ntfs_set_error_and_exit(EINVAL);
|
|
|
|
USBHSFS_LOG_MSG("Closing file %lu.", file->ni->mft_no);
|
|
|
|
/* If the file is dirty, synchronize its data. */
|
|
if (NInoDirty(file->ni)) ntfs_inode_sync(file->ni);
|
|
|
|
/* Special case clean-ups for compressed and/or encrypted files. */
|
|
if (file->compressed) ntfs_attr_pclose(file->data);
|
|
|
|
#ifdef HAVE_SETXATTR
|
|
if (file->encrypted) ntfs_efs_fixup_attribute(NULL, file->data);
|
|
#endif
|
|
|
|
/* Close file data attribute. */
|
|
ntfs_attr_close(file->data);
|
|
|
|
/* Close file node. */
|
|
ntfs_inode_close(file->ni);
|
|
|
|
/* Reset file state. */
|
|
memset(file, 0, sizeof(ntfs_file_state));
|
|
|
|
end:
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return(0);
|
|
}
|
|
|
|
static ssize_t ntfsdev_write(struct _reent *r, void *fd, const char *ptr, size_t len)
|
|
{
|
|
size_t wr_sz = 0;
|
|
|
|
ntfs_declare_error_state;
|
|
ntfs_declare_file_state;
|
|
ntfs_lock_drive_ctx;
|
|
|
|
/* Sanity check. */
|
|
if (!file || !file->ni || !file->data || !ptr || !len) ntfs_set_error_and_exit(EINVAL);
|
|
|
|
/* Check if the file was opened with write access. */
|
|
if (!file->write) ntfs_set_error_and_exit(EBADF);
|
|
|
|
/* Check if the append flag is enabled. */
|
|
if (file->append) file->pos = file->len;
|
|
|
|
/* Write file data until the requested length is satified. */
|
|
/* This is done like this because writing to compressed files may return partial write sizes instead of the full size in a single call. */
|
|
while(len > 0)
|
|
{
|
|
USBHSFS_LOG_MSG("Writing 0x%lX byte(s) to file %lu at offset 0x%lX.", len, file->ni->mft_no, file->pos);
|
|
|
|
s64 written = ntfs_attr_pwrite(file->data, (s64)file->pos, (s64)len, ptr);
|
|
if (written <= 0 || written > (s64)len) ntfs_set_error_and_exit(errno);
|
|
|
|
wr_sz += written;
|
|
file->pos += written;
|
|
len -= written;
|
|
ptr += written;
|
|
}
|
|
|
|
end:
|
|
/* Did we write anything? */
|
|
if (file && wr_sz > 0)
|
|
{
|
|
/* Mark the file as dirty. */
|
|
NInoSetDirty(file->ni);
|
|
|
|
/* Mark the file for archiving. */
|
|
file->ni->flags |= FILE_ATTR_ARCHIVE;
|
|
|
|
/* Update file last access and modify times. */
|
|
ntfs_inode_update_times_filtered(file->vd, file->ni, NTFS_UPDATE_AMCTIME);
|
|
|
|
/* Update the files data length. */
|
|
file->len = file->data->data_size;
|
|
}
|
|
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return((ssize_t)wr_sz);
|
|
}
|
|
|
|
static ssize_t ntfsdev_read(struct _reent *r, void *fd, char *ptr, size_t len)
|
|
{
|
|
size_t rd_sz = 0;
|
|
|
|
ntfs_declare_error_state;
|
|
ntfs_declare_file_state;
|
|
ntfs_lock_drive_ctx;
|
|
|
|
/* Sanity check. */
|
|
if (!file || !file->ni || !file->data || !ptr || !len) ntfs_set_error_and_exit(EINVAL);
|
|
|
|
/* Check if the file was opened with read access. */
|
|
if (!file->read) ntfs_set_error_and_exit(EBADF);
|
|
|
|
/* Don't read past EOF. */
|
|
if (file->pos + len > file->len)
|
|
{
|
|
r->_errno = EOVERFLOW;
|
|
len = (file->len - file->pos);
|
|
}
|
|
|
|
/* Read file data until the requested length is satified. */
|
|
/* This is done like this because reading from compressed files may return partial read sizes instead of the full size in a single call. */
|
|
while(len > 0)
|
|
{
|
|
USBHSFS_LOG_MSG("Reading 0x%lX byte(s) from file %lu at offset 0x%lX.", len, file->ni->mft_no, file->pos);
|
|
|
|
s64 read = ntfs_attr_pread(file->data, (s64)file->pos, (s64)len, ptr);
|
|
if (read <= 0 || read > (s64)len) ntfs_set_error_and_exit(errno);
|
|
|
|
rd_sz += read;
|
|
file->pos += read;
|
|
len -= read;
|
|
ptr += read;
|
|
}
|
|
|
|
end:
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return((ssize_t)rd_sz);
|
|
}
|
|
|
|
static off_t ntfsdev_seek(struct _reent *r, void *fd, off_t pos, int dir)
|
|
{
|
|
off_t offset = 0;
|
|
|
|
ntfs_declare_error_state;
|
|
ntfs_declare_file_state;
|
|
ntfs_lock_drive_ctx;
|
|
|
|
/* Sanity check. */
|
|
if (!file || !file->ni || !file->data) ntfs_set_error_and_exit(EINVAL);
|
|
|
|
/* Find the offset to seek from. */
|
|
switch(dir)
|
|
{
|
|
case SEEK_SET: /* Set absolute position relative to zero (start offset). */
|
|
file->pos = MIN(MAX(pos, 0), file->len);
|
|
break;
|
|
case SEEK_CUR: /* Set position relative to the current position. */
|
|
file->pos = MIN(MAX(file->pos + pos, 0), file->len);
|
|
break;
|
|
case SEEK_END: /* Set position relative to EOF. */
|
|
file->pos = MIN(MAX(file->len + pos, 0), file->len);
|
|
break;
|
|
default: /* Invalid option. */
|
|
ntfs_set_error_and_exit(EINVAL);
|
|
}
|
|
|
|
USBHSFS_LOG_MSG("Seeking to offset 0x%lX from file in %lu.", file->pos, file->ni->mft_no);
|
|
offset = file->pos;
|
|
|
|
end:
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return(offset);
|
|
}
|
|
|
|
static int ntfsdev_fstat(struct _reent *r, void *fd, struct stat *st)
|
|
{
|
|
ntfs_declare_error_state;
|
|
ntfs_declare_file_state;
|
|
ntfs_lock_drive_ctx;
|
|
|
|
/* Sanity check. */
|
|
if (!file || !file->ni || !file->data || !st) ntfs_set_error_and_exit(EINVAL);
|
|
|
|
USBHSFS_LOG_MSG("Getting file stats for %lu.", file->ni->mft_no);
|
|
|
|
/* Get file stats. */
|
|
ntfsdev_fill_stat(file->vd, file->ni, st);
|
|
|
|
end:
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return(0);
|
|
}
|
|
|
|
static int ntfsdev_stat(struct _reent *r, const char *path, struct stat *st)
|
|
{
|
|
ntfs_inode *ni = NULL;
|
|
|
|
ntfs_declare_error_state;
|
|
ntfs_lock_drive_ctx;
|
|
ntfs_declare_vol_state;
|
|
|
|
/* Sanity check. */
|
|
if (!st) ntfs_set_error_and_exit(EINVAL);
|
|
|
|
/* Fix input path. */
|
|
if (!ntfsdev_fixpath(r, path, &fs_ctx, NULL)) ntfs_end;
|
|
|
|
USBHSFS_LOG_MSG("Getting stats for \"%s\" (\"%s\").", path, __usbhsfs_dev_path_buf);
|
|
|
|
/* Get entry. */
|
|
ni = ntfs_inode_open_from_path(vd, __usbhsfs_dev_path_buf);
|
|
if (!ni) ntfs_set_error_and_exit(errno);
|
|
|
|
/* Get entry stats. */
|
|
ntfsdev_fill_stat(vd, ni, st);
|
|
|
|
end:
|
|
if (ni) ntfs_inode_close(ni);
|
|
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return(0);
|
|
}
|
|
|
|
static int ntfsdev_link(struct _reent *r, const char *existing, const char *newLink)
|
|
{
|
|
char existing_path[MAX_PATH_LENGTH] = {0};
|
|
char *new_path = __usbhsfs_dev_path_buf;
|
|
ntfs_inode *ni = NULL;
|
|
|
|
ntfs_declare_error_state;
|
|
ntfs_lock_drive_ctx;
|
|
ntfs_declare_vol_state;
|
|
|
|
/* Fix input paths. */
|
|
if (!ntfsdev_fixpath(r, existing, &fs_ctx, existing_path) || !ntfsdev_fixpath(r, newLink, &fs_ctx, new_path)) ntfs_end;
|
|
|
|
USBHSFS_LOG_MSG("Linking \"%s\" (\"%s\") to \"%s\" (\"%s\").", existing, existing_path, newLink, new_path);
|
|
|
|
/* Create a symbolic link entry. */
|
|
ni = ntfs_inode_create(vd, existing_path, S_IFLNK, new_path);
|
|
if (!ni) ntfs_set_error(errno);
|
|
|
|
end:
|
|
if (ni) ntfs_inode_close(ni);
|
|
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return(0);
|
|
}
|
|
|
|
static int ntfsdev_unlink(struct _reent *r, const char *name)
|
|
{
|
|
ntfs_declare_error_state;
|
|
ntfs_lock_drive_ctx;
|
|
ntfs_declare_vol_state;
|
|
|
|
/* Fix input path. */
|
|
if (!ntfsdev_fixpath(r, name, &fs_ctx, NULL)) ntfs_end;
|
|
|
|
USBHSFS_LOG_MSG("Deleting \"%s\" (\"%s\").", name, __usbhsfs_dev_path_buf);
|
|
|
|
/* Unlink entry. */
|
|
if (ntfs_inode_unlink(vd, __usbhsfs_dev_path_buf)) ntfs_set_error(errno);
|
|
|
|
end:
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return(0);
|
|
}
|
|
|
|
static int ntfsdev_chdir(struct _reent *r, const char *name)
|
|
{
|
|
ntfs_inode *ni = NULL;
|
|
size_t cwd_len = 0;
|
|
|
|
ntfs_declare_error_state;
|
|
ntfs_lock_drive_ctx;
|
|
ntfs_declare_vol_state;
|
|
|
|
/* Fix input path. */
|
|
if (!ntfsdev_fixpath(r, name, &fs_ctx, NULL)) ntfs_end;
|
|
|
|
USBHSFS_LOG_MSG("Changing current directory to \"%s\" (\"%s\").", name, __usbhsfs_dev_path_buf);
|
|
|
|
/* Find directory entry. */
|
|
ni = ntfs_inode_open_from_path(vd, __usbhsfs_dev_path_buf);
|
|
if (!ni) ntfs_set_error_and_exit(ENOENT);
|
|
|
|
/* Make sure this is indeed a directory. */
|
|
if (!(ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) ntfs_set_error_and_exit(ENOTDIR);
|
|
|
|
/* Update current working directory. */
|
|
sprintf(fs_ctx->cwd, "%s", __usbhsfs_dev_path_buf);
|
|
|
|
cwd_len = strlen(fs_ctx->cwd);
|
|
if (fs_ctx->cwd[cwd_len - 1] != '/')
|
|
{
|
|
fs_ctx->cwd[cwd_len] = '/';
|
|
fs_ctx->cwd[cwd_len + 1] = '\0';
|
|
}
|
|
|
|
/* Set default devoptab device. */
|
|
usbHsFsMountSetDefaultDevoptabDevice(fs_ctx);
|
|
|
|
end:
|
|
if (ni) ntfs_inode_close(ni);
|
|
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return(0);
|
|
}
|
|
|
|
static int ntfsdev_rename(struct _reent *r, const char *oldName, const char *newName)
|
|
{
|
|
char old_path[MAX_PATH_LENGTH] = {0};
|
|
char *new_path = __usbhsfs_dev_path_buf;
|
|
ntfs_inode *ni = NULL;
|
|
|
|
ntfs_declare_error_state;
|
|
ntfs_lock_drive_ctx;
|
|
ntfs_declare_vol_state;
|
|
|
|
/* Fix input paths. */
|
|
if (!ntfsdev_fixpath(r, oldName, &fs_ctx, old_path) || !ntfsdev_fixpath(r, newName, &fs_ctx, new_path)) ntfs_end;
|
|
|
|
/* Check if there's an entry with the new name. */
|
|
ni = ntfs_inode_open_from_path(vd, new_path);
|
|
if (ni)
|
|
{
|
|
ntfs_inode_close(ni);
|
|
ntfs_set_error_and_exit(EEXIST);
|
|
}
|
|
|
|
USBHSFS_LOG_MSG("Renaming \"%s\" (\"%s\") to \"%s\" (\"%s\").", oldName, old_path, newName, new_path);
|
|
|
|
/* Link the old entry with the new one. */
|
|
if (ntfs_inode_link(vd, old_path, new_path)) ntfs_set_error_and_exit(errno);
|
|
|
|
/* Unlink the old entry. */
|
|
if (ntfs_inode_unlink(vd, old_path)) ntfs_set_error(errno);
|
|
|
|
end:
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return(0);
|
|
}
|
|
|
|
static int ntfsdev_mkdir(struct _reent *r, const char *path, int mode)
|
|
{
|
|
(void)mode;
|
|
|
|
ntfs_inode *ni = NULL;
|
|
|
|
ntfs_declare_error_state;
|
|
ntfs_lock_drive_ctx;
|
|
ntfs_declare_vol_state;
|
|
|
|
/* Fix input path. */
|
|
if (!ntfsdev_fixpath(r, path, &fs_ctx, NULL)) ntfs_end;
|
|
|
|
USBHSFS_LOG_MSG("Creating directory \"%s\" (\"%s\").", path, __usbhsfs_dev_path_buf);
|
|
|
|
/* Create directory. */
|
|
ni = ntfs_inode_create(vd, __usbhsfs_dev_path_buf, S_IFDIR, NULL);
|
|
if (!ni) ntfs_set_error(errno);
|
|
|
|
end:
|
|
if (ni) ntfs_inode_close(ni);
|
|
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return(0);
|
|
}
|
|
|
|
static DIR_ITER *ntfsdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path)
|
|
{
|
|
ntfs_dir_state *dir = NULL;
|
|
DIR_ITER *ret = NULL;
|
|
|
|
ntfs_declare_error_state;
|
|
ntfs_lock_drive_ctx;
|
|
ntfs_declare_vol_state;
|
|
|
|
/* Sanity check. */
|
|
if (!dirState) ntfs_set_error_and_exit(EINVAL);
|
|
|
|
/* Fix input path. */
|
|
if (!ntfsdev_fixpath(r, path, &fs_ctx, NULL)) ntfs_end;
|
|
|
|
/* Setup directory descriptor. */
|
|
dir = (ntfs_dir_state*)dirState->dirStruct;
|
|
memset(dir, 0, sizeof(ntfs_dir_state));
|
|
dir->vd = vd;
|
|
|
|
USBHSFS_LOG_MSG("Opening directory \"%s\" (\"%s\").", path, __usbhsfs_dev_path_buf);
|
|
|
|
/* Open directory. */
|
|
dir->ni = ntfs_inode_open_from_path(dir->vd, __usbhsfs_dev_path_buf);
|
|
if (!dir->ni) ntfs_set_error_and_exit(errno);
|
|
|
|
/* Make sure this is indeed a directory. */
|
|
if (!(dir->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) ntfs_set_error_and_exit(ENOTDIR);
|
|
|
|
/* Update directory last access time. */
|
|
ntfs_inode_update_times_filtered(dir->vd, dir->ni, NTFS_UPDATE_ATIME);
|
|
|
|
/* Update return value. */
|
|
ret = dirState;
|
|
|
|
end:
|
|
/* Clean up if something went wrong. */
|
|
if (ntfs_ended_with_error && dir)
|
|
{
|
|
if (dir->ni) ntfs_inode_close(dir->ni);
|
|
|
|
memset(dir, 0, sizeof(ntfs_dir_state));
|
|
}
|
|
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return_ptr(ret);
|
|
}
|
|
|
|
static int ntfsdev_dirreset(struct _reent *r, DIR_ITER *dirState)
|
|
{
|
|
ntfs_declare_error_state;
|
|
ntfs_lock_drive_ctx;
|
|
|
|
/* Sanity check. */
|
|
if (!dirState) ntfs_set_error_and_exit(EINVAL);
|
|
|
|
ntfs_declare_dir_state;
|
|
|
|
/* Sanity check. */
|
|
if (!dir->vd || !dir->ni) ntfs_set_error_and_exit(EINVAL);
|
|
|
|
USBHSFS_LOG_MSG("Resetting directory state for %lu.", dir->ni->mft_no);
|
|
|
|
/* Reset directory position. */
|
|
dir->pos = 0;
|
|
|
|
/* Free directory entries. */
|
|
while(dir->first)
|
|
{
|
|
ntfs_dir_entry *next = dir->first->next;
|
|
if (dir->first->name) free(dir->first->name);
|
|
free(dir->first);
|
|
dir->first = next;
|
|
}
|
|
|
|
dir->first = dir->current = NULL;
|
|
|
|
end:
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return(0);
|
|
}
|
|
|
|
static int ntfsdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat)
|
|
{
|
|
ntfs_inode *ni = NULL;
|
|
|
|
ntfs_declare_error_state;
|
|
ntfs_lock_drive_ctx;
|
|
|
|
/* Sanity check. */
|
|
if (!dirState || !filename || !filestat) ntfs_set_error_and_exit(EINVAL);
|
|
|
|
ntfs_declare_dir_state;
|
|
|
|
/* Sanity check. */
|
|
if (!dir->vd || !dir->ni) ntfs_set_error_and_exit(EINVAL);
|
|
|
|
if (!dir->pos && !dir->first)
|
|
{
|
|
USBHSFS_LOG_MSG("Directory %lu hasn't been read. Caching all entries first.", dir->ni->mft_no);
|
|
|
|
/* Read directory contents. */
|
|
if (ntfs_readdir(dir->ni, &(dir->pos), dirState, ntfsdev_dirnext_filldir)) ntfs_set_error_and_exit(errno);
|
|
|
|
/* Move to the first entry in the directory. */
|
|
dir->current = dir->first;
|
|
|
|
/* Update directory last access time. */
|
|
ntfs_inode_update_times_filtered(dir->vd, dir->ni, NTFS_UPDATE_ATIME);
|
|
}
|
|
|
|
/* Check if there's an entry waiting to be fetched (end of directory). */
|
|
if (!dir->current || !dir->current->name) ntfs_set_error_and_exit(ENOENT);
|
|
|
|
USBHSFS_LOG_MSG("Getting info from next directory %lu entry.", dir->ni->mft_no);
|
|
|
|
/* Retrieve inode for the fetched entry. */
|
|
ni = ntfs_pathname_to_inode(dir->vd->vol, dir->ni, dir->current->name);
|
|
if (!ni) ntfs_set_error_and_exit(errno);
|
|
|
|
/* Copy entry name. */
|
|
strcpy(filename, dir->current->name);
|
|
|
|
/* Get entry stats. */
|
|
ntfsdev_fill_stat(dir->vd, ni, filestat);
|
|
|
|
/* Close inode, we don't need it anymore. */
|
|
ntfs_inode_close(ni);
|
|
|
|
/* Move to the next entry in the directory. */
|
|
dir->current = dir->current->next;
|
|
|
|
/* Update directory position. */
|
|
dir->pos++;
|
|
|
|
end:
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return(0);
|
|
}
|
|
|
|
static int ntfsdev_dirclose(struct _reent *r, DIR_ITER *dirState)
|
|
{
|
|
ntfs_declare_error_state;
|
|
ntfs_lock_drive_ctx;
|
|
|
|
/* Sanity check. */
|
|
if (!dirState) ntfs_set_error_and_exit(EINVAL);
|
|
|
|
ntfs_declare_dir_state;
|
|
|
|
USBHSFS_LOG_MSG("Closing directory %lu.", dir->ni->mft_no);
|
|
|
|
/* Free directory entries. */
|
|
while(dir->first)
|
|
{
|
|
ntfs_dir_entry *next = dir->first->next;
|
|
if (dir->first->name) free(dir->first->name);
|
|
free(dir->first);
|
|
dir->first = next;
|
|
}
|
|
|
|
/* Close directory node. */
|
|
if (dir->ni) ntfs_inode_close(dir->ni);
|
|
|
|
/* Reset directory state. */
|
|
memset(dir, 0, sizeof(ntfs_dir_state));
|
|
|
|
end:
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return(0);
|
|
}
|
|
|
|
static int ntfsdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf)
|
|
{
|
|
(void)path;
|
|
|
|
s64 size = 0;
|
|
s8 delta_bits = 0;
|
|
|
|
ntfs_declare_error_state;
|
|
ntfs_lock_drive_ctx;
|
|
ntfs_declare_vol_state;
|
|
|
|
/* Sanity check. */
|
|
if (!buf) ntfs_set_error_and_exit(EINVAL);
|
|
|
|
USBHSFS_LOG_MSG("Getting filesystem stats for \"%s\".", path);
|
|
|
|
/* Check available free space, if not already known. */
|
|
if (!NVolFreeSpaceKnown(vd->vol) && ntfs_volume_get_free_space(vd->vol) < 0) ntfs_set_error_and_exit(ENOSPC);
|
|
|
|
/* Determine free cluster count. */
|
|
size = MAX(vd->vol->free_clusters, 0);
|
|
|
|
/* Determine free inodes within the free space. */
|
|
delta_bits = (s8)(vd->vol->cluster_size_bits - vd->vol->mft_record_size_bits);
|
|
|
|
/* Fill filesystem stats. */
|
|
memset(buf, 0, sizeof(struct statvfs));
|
|
|
|
buf->f_bsize = vd->vol->cluster_size; /* Filesystem sector size. */
|
|
buf->f_frsize = vd->vol->cluster_size; /* Fundamental filesystem sector size. */
|
|
buf->f_blocks = vd->vol->nr_clusters; /* Total number of sectors in filesystem (in f_frsize units). */
|
|
buf->f_bfree = size; /* Free sectors. */
|
|
buf->f_bavail = buf->f_bfree; /* Available sectors. */
|
|
buf->f_files = ((vd->vol->mftbmp_na->allocated_size << 3) + (delta_bits >= 0 ? (size <<= delta_bits) : (size >>= -delta_bits))); /* Total number of inodes in file system. */
|
|
buf->f_ffree = MAX(size + vd->vol->free_mft_records, 0); /* Free inodes. */
|
|
buf->f_favail = buf->f_ffree; /* Available inodes. */
|
|
buf->f_fsid = vd->id; /* Filesystem ID. */
|
|
buf->f_flag = (NVolReadOnly(vd->vol) ? ST_RDONLY : 0); /* Filesystem flags. */
|
|
buf->f_namemax = NTFS_MAX_NAME_LEN; /* Max filename length. */
|
|
|
|
end:
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return(0);
|
|
}
|
|
|
|
static int ntfsdev_ftruncate(struct _reent *r, void *fd, off_t len)
|
|
{
|
|
char zero = 0;
|
|
|
|
ntfs_declare_error_state;
|
|
ntfs_declare_file_state;
|
|
ntfs_lock_drive_ctx;
|
|
|
|
/* Sanity check. */
|
|
if (!file || !file->ni || !file->data || len < 0) ntfs_set_error_and_exit(EINVAL);
|
|
|
|
/* Check if the file was opened with write access. */
|
|
if (!file->write) ntfs_set_error_and_exit(EBADF);
|
|
|
|
/* For compressed files, only deleting and expanding contents are implemented. */
|
|
if (file->compressed && len > 0 && len < file->data->initialized_size) ntfs_set_error_and_exit(EOPNOTSUPP);
|
|
|
|
USBHSFS_LOG_MSG("Truncating file in %lu to 0x%lX bytes.", file->ni->mft_no, len);
|
|
|
|
if (len > file->data->initialized_size)
|
|
{
|
|
/* Expand file data attribute. */
|
|
if (ntfs_attr_pwrite(file->data, len - 1, 1, &zero) <= 0) ntfs_set_error(errno);
|
|
} else {
|
|
/* Truncate file data attribute. */
|
|
if (ntfs_attr_truncate(file->data, len)) ntfs_set_error(errno);
|
|
}
|
|
|
|
end:
|
|
/* Did the file size actually change? */
|
|
if (file && file->len != (u64)file->data->data_size)
|
|
{
|
|
/* Mark the file as dirty. */
|
|
NInoSetDirty(file->ni);
|
|
|
|
/* Mark the file for archiving. */
|
|
file->ni->flags |= FILE_ATTR_ARCHIVE;
|
|
|
|
/* Update file last access and modify times. */
|
|
ntfs_inode_update_times_filtered(file->vd, file->ni, NTFS_UPDATE_AMCTIME);
|
|
|
|
/* Update file data length. */
|
|
file->len = file->data->data_size;
|
|
}
|
|
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return(0);
|
|
}
|
|
|
|
static int ntfsdev_fsync(struct _reent *r, void *fd)
|
|
{
|
|
ntfs_declare_error_state;
|
|
ntfs_declare_file_state;
|
|
ntfs_lock_drive_ctx;
|
|
|
|
/* Sanity check. */
|
|
if (!file || !file->ni || !file->data) ntfs_set_error_and_exit(EINVAL);
|
|
|
|
USBHSFS_LOG_MSG("Synchronizing data for file in %lu.", file->ni->mft_no);
|
|
|
|
/* Synchronize file. */
|
|
if (ntfs_inode_sync(file->ni)) ntfs_set_error_and_exit(errno);
|
|
|
|
/* Clear dirty status from file. */
|
|
NInoClearDirty(file->ni);
|
|
|
|
end:
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return(0);
|
|
}
|
|
|
|
static int ntfsdev_chmod(struct _reent *r, const char *path, mode_t mode)
|
|
{
|
|
(void)path;
|
|
(void)mode;
|
|
|
|
/* Not implemented. */
|
|
r->_errno = ENOSYS;
|
|
return -1;
|
|
}
|
|
|
|
static int ntfsdev_fchmod(struct _reent *r, void *fd, mode_t mode)
|
|
{
|
|
(void)fd;
|
|
(void)mode;
|
|
|
|
/* Not implemented. */
|
|
r->_errno = ENOSYS;
|
|
return -1;
|
|
}
|
|
|
|
static int ntfsdev_rmdir(struct _reent *r, const char *name)
|
|
{
|
|
/* Exactly the same as ntfsdev_unlink(). */
|
|
return ntfsdev_unlink(r, name);
|
|
}
|
|
|
|
static int ntfsdev_utimes(struct _reent *r, const char *filename, const struct timeval times[2])
|
|
{
|
|
ntfs_inode *ni = NULL;
|
|
struct timespec ts_times[2] = {0};
|
|
ntfs_time ntfs_times[3] = {0};
|
|
|
|
ntfs_declare_error_state;
|
|
ntfs_lock_drive_ctx;
|
|
ntfs_declare_vol_state;
|
|
|
|
/* Fix input path. */
|
|
if (!ntfsdev_fixpath(r, filename, &fs_ctx, NULL)) ntfs_end;
|
|
|
|
/* Get entry. */
|
|
ni = ntfs_inode_open_from_path(vd, __usbhsfs_dev_path_buf);
|
|
if (!ni) ntfs_set_error_and_exit(errno);
|
|
|
|
/* Check if we should use the current time. */
|
|
if (!times)
|
|
{
|
|
/* Get current time. */
|
|
clock_gettime(CLOCK_REALTIME, &(ts_times[0]));
|
|
memcpy(&(ts_times[1]), &(ts_times[0]), sizeof(struct timespec));
|
|
} else {
|
|
/* Convert provided timeval values to timespec values. */
|
|
TIMEVAL_TO_TIMESPEC(&(times[0]), &(ts_times[0]));
|
|
TIMEVAL_TO_TIMESPEC(&(times[1]), &(ts_times[1]));
|
|
}
|
|
|
|
/* Convert POSIX timespec values to Microsoft's NTFS time values. */
|
|
/* utimes() expects an array with this order: access, update. */
|
|
/* ntfs_inode_set_times() expects an array with this order: create, update, access. */
|
|
/* We will preserve the creation time. */
|
|
ntfs_times[0] = ni->creation_time;
|
|
ntfs_times[1] = timespec2ntfs(ts_times[1]);
|
|
ntfs_times[2] = timespec2ntfs(ts_times[0]);
|
|
|
|
USBHSFS_LOG_MSG("Setting last access and modification times for \"%s\" (\"%s\") to 0x%lX and 0x%lX, respectively.", filename, __usbhsfs_dev_path_buf, ntfs_times[2], ntfs_times[1]);
|
|
|
|
/* Change timestamps. */
|
|
if (ntfs_inode_set_times(ni, (const char*)ntfs_times, sizeof(ntfs_times), 0)) ntfs_set_error(errno);
|
|
|
|
end:
|
|
if (ni) ntfs_inode_close(ni);
|
|
|
|
ntfs_unlock_drive_ctx;
|
|
ntfs_return(0);
|
|
}
|
|
|
|
static bool ntfsdev_fixpath(struct _reent *r, const char *path, UsbHsFsDriveLogicalUnitFileSystemContext **fs_ctx, char *outpath)
|
|
{
|
|
const u8 *p = (const u8*)path;
|
|
ssize_t units = 0;
|
|
u32 code = 0;
|
|
size_t cwd_len = 0, full_len = 0, i = 0;
|
|
char *outptr = (outpath ? outpath : __usbhsfs_dev_path_buf), *cwd = NULL, *segment = NULL, *path_sep = NULL;
|
|
int depth = 0;
|
|
bool finished = false;
|
|
|
|
ntfs_declare_error_state;
|
|
|
|
if (!r || !path || !*path || !fs_ctx || !*fs_ctx || !(cwd = (*fs_ctx)->cwd)) ntfs_set_error_and_exit(EINVAL);
|
|
|
|
USBHSFS_LOG_MSG("Input path: \"%s\".", path);
|
|
|
|
/* Move the path pointer to the start of the actual path. */
|
|
do {
|
|
units = decode_utf8(&code, p);
|
|
if (units < 0) ntfs_set_error_and_exit(EILSEQ);
|
|
p += units;
|
|
} while(code >= ' ' && code != ':');
|
|
|
|
/* We found a colon; p points to the actual path. */
|
|
if (code == ':') path = (const char*)p;
|
|
|
|
/* Verify absolute path length. */
|
|
cwd_len = strlen(cwd);
|
|
full_len = strlen(path);
|
|
if (path[0] != '/') full_len += cwd_len;
|
|
|
|
if (full_len >= MAX_PATH_LENGTH) ntfs_set_error_and_exit(ENAMETOOLONG);
|
|
|
|
/* Follow path and build the fixed path. NTFS-3G doesn't seem to support dot entries in input paths. */
|
|
/* We'll also replace consecutive path separators with a single one, make sure there are no more colons nor NT path separators, and check if the remainder of the string is valid UTF-8. */
|
|
if (path[0] == '/')
|
|
{
|
|
sprintf(outptr, "/");
|
|
p = (const u8*)(path + 1);
|
|
} else {
|
|
sprintf(outptr, "%s", cwd);
|
|
p = (const u8*)path;
|
|
|
|
for(i = 1; i < cwd_len; i++)
|
|
{
|
|
if (cwd[i] == '/') depth++;
|
|
}
|
|
}
|
|
|
|
i = strlen(outptr);
|
|
segment = (outptr + i);
|
|
|
|
while(true)
|
|
{
|
|
do {
|
|
units = decode_utf8(&code, p);
|
|
|
|
/* Don't tolerate invalid UTF-8, colons or NT path separators. */
|
|
if (units < 0) ntfs_set_error_and_exit(EILSEQ);
|
|
if (code == ':' || code == '\\') ntfs_set_error_and_exit(EINVAL);
|
|
|
|
/* Copy character(s) if needed. */
|
|
if (code >= ' ' && code != '/')
|
|
{
|
|
memcpy(outptr + i, p, units);
|
|
i += units;
|
|
p += units;
|
|
}
|
|
} while(code >= ' ' && code != '/');
|
|
|
|
if (code < ' ')
|
|
{
|
|
/* Reached the end of the string. */
|
|
outptr[i] = '\0';
|
|
finished = true;
|
|
} else
|
|
if (code == '/')
|
|
{
|
|
/* Found a path separator. Let's skip consecutive path separators if they exist. */
|
|
while(*p == '/') p++;
|
|
}
|
|
|
|
if (!strcmp(segment, "."))
|
|
{
|
|
/* We're dealing with a current directory dot entry alias. */
|
|
/* We'll simply remove it from the fixed path. */
|
|
outptr[--i] = '\0';
|
|
} else
|
|
if (!strcmp(segment, ".."))
|
|
{
|
|
/* We're dealing with a parent directory dot entry alias. */
|
|
/* First check if we aren't currently at the root directory. */
|
|
if (depth <= 0) ntfs_set_error_and_exit(EINVAL);
|
|
|
|
/* Remove dot entry and path separator from the output string. */
|
|
outptr[i - 1] = outptr[i - 2] = outptr[i - 3] = '\0';
|
|
i -= 3;
|
|
|
|
/* Find previous ocurrence of a path separator in the output string. */
|
|
path_sep = strrchr(outptr + i, '/');
|
|
if (!path_sep) ntfs_set_error_and_exit(EINVAL);
|
|
*(++path_sep) = '\0';
|
|
|
|
/* Update current segment and directory depth. */
|
|
segment = path_sep;
|
|
depth--;
|
|
} else {
|
|
/* New entry in the directory tree. */
|
|
/* Check its length. */
|
|
if (strlen(segment) > NTFS_MAX_NAME_LEN) ntfs_set_error_and_exit(ENAMETOOLONG);
|
|
|
|
/* Update output string, current segment and depth. */
|
|
outptr[i++] = '/';
|
|
outptr[i] = '\0';
|
|
|
|
segment = (outptr + i);
|
|
depth++;
|
|
}
|
|
|
|
if (finished) break;
|
|
}
|
|
|
|
/* Remove trailing path separator. */
|
|
if (i > 1 && outptr[i - 1] == '/') outptr[--i] = '\0';
|
|
|
|
USBHSFS_LOG_MSG("Fixed path: \"%s\".", outptr);
|
|
|
|
end:
|
|
ntfs_return_bool;
|
|
}
|
|
|
|
static void ntfsdev_fill_stat(ntfs_vd *vd, ntfs_inode *ni, struct stat *st)
|
|
{
|
|
ntfs_attr *na = NULL;
|
|
|
|
/* Clear stat struct. */
|
|
memset(st, 0, sizeof(struct stat));
|
|
|
|
/* Fill stat struct. */
|
|
st->st_dev = vd->id;
|
|
st->st_ino = ni->mft_no;
|
|
st->st_uid = vd->uid;
|
|
st->st_gid = vd->gid;
|
|
|
|
if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
|
|
{
|
|
/* We're dealing with a directory entry. */
|
|
st->st_mode = (S_IFDIR | (0777 & ~vd->dmask));
|
|
st->st_nlink = 1;
|
|
|
|
/* Open the directory index allocation table attribute to get size stats. */
|
|
na = ntfs_attr_open(ni, AT_INDEX_ALLOCATION, NTFS_INDEX_I30, 4);
|
|
if (na)
|
|
{
|
|
st->st_size = na->data_size;
|
|
st->st_blocks = (na->allocated_size >> 9);
|
|
ntfs_attr_close(na);
|
|
}
|
|
} else {
|
|
/* We're dealing with a file entry. */
|
|
st->st_mode = (S_IFREG | (0777 & ~vd->fmask));
|
|
st->st_nlink = le16_to_cpu(ni->mrec->link_count);
|
|
st->st_size = ni->data_size;
|
|
st->st_blocks = ((ni->allocated_size + 511) >> 9);
|
|
}
|
|
|
|
/* Convert Microsoft's NTFS time values to POSIX timespec values. */
|
|
st->st_atim = ntfs2timespec(ni->last_access_time);
|
|
st->st_mtim = ntfs2timespec(ni->last_data_change_time);
|
|
st->st_ctim = ntfs2timespec(ni->creation_time);
|
|
}
|
|
|
|
static int ntfsdev_dirnext_filldir(void *dirent, const ntfschar *name, const int name_len, const int name_type, const s64 pos, const MFT_REF mref, const unsigned dt_type)
|
|
{
|
|
(void)pos;
|
|
(void)dt_type;
|
|
|
|
DIR_ITER *dirState = (DIR_ITER*)dirent;
|
|
ntfs_inode *ni = NULL;
|
|
ntfs_dir_entry *entry = NULL;
|
|
char *entry_name = NULL;
|
|
bool cleanup = false;
|
|
|
|
ntfs_declare_error_state;
|
|
ntfs_declare_dir_state;
|
|
|
|
/* Ignore DOS file names. */
|
|
if (name_type == FILE_NAME_DOS) ntfs_end;
|
|
|
|
/* Convert the entry name from UTF-16LE into our current locale (UTF-8). */
|
|
if (ntfs_ucstombs(name, name_len, &entry_name, 0) <= 0)
|
|
{
|
|
_errno = errno;
|
|
ntfs_end;
|
|
}
|
|
|
|
/* Skip parent and current directory entries (dot entries). */
|
|
if (!strcmp(entry_name, ".") || !strcmp(entry_name, ".."))
|
|
{
|
|
cleanup = true;
|
|
ntfs_end;
|
|
}
|
|
|
|
/* Open entry. */
|
|
ni = ntfs_pathname_to_inode(dir->vd->vol, dir->ni, entry_name);
|
|
if (!ni)
|
|
{
|
|
_errno = errno;
|
|
ntfs_end;
|
|
}
|
|
|
|
USBHSFS_LOG_MSG("Found entry \"%s\" with MREF %lu.", entry_name, mref);
|
|
|
|
/* Allocate a new directory entry. */
|
|
entry = malloc(sizeof(ntfs_dir_entry));
|
|
if (!entry)
|
|
{
|
|
_errno = ENOMEM;
|
|
ntfs_end;
|
|
}
|
|
|
|
/* Setup the directory entry. */
|
|
entry->mref = MREF(mref);
|
|
entry->name = entry_name;
|
|
entry->next = NULL;
|
|
|
|
/* Link entry to the list of directory entries. */
|
|
if (!dir->first)
|
|
{
|
|
dir->first = entry;
|
|
} else {
|
|
ntfs_dir_entry *last = dir->first;
|
|
while(last->next) last = last->next;
|
|
last->next = entry;
|
|
}
|
|
|
|
end:
|
|
if (ni) ntfs_inode_close(ni);
|
|
|
|
/* Cleanup if we failed to enumerate the entry or if it is meant to be skipped. */
|
|
if (ntfs_ended_with_error || cleanup)
|
|
{
|
|
if (entry_name) free(entry_name);
|
|
if (entry) free(entry);
|
|
}
|
|
|
|
ntfs_return(0);
|
|
}
|