2023-09-05 02:09:05 +01:00

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);
}