mirror of
https://github.com/hax4dazy/TinWoo.git
synced 2025-02-09 19:25:05 +01:00
346 lines
11 KiB
C
346 lines
11 KiB
C
/*
|
|
* ntfs.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).
|
|
*/
|
|
|
|
#include "ntfs.h"
|
|
|
|
/* Type definitions. */
|
|
|
|
/// NTFS path.
|
|
typedef struct _ntfs_path {
|
|
const char *path; ///< Volume path (e.g. '/foo/bar/file.txt').
|
|
const char *dir; ///< Directory path (e.g. '/foo/bar').
|
|
const char *name; ///< Filename (e.g. 'file.txt').
|
|
char buf[MAX_PATH_LENGTH]; ///< Internal buffer containing the path string.
|
|
} ntfs_path;
|
|
|
|
/* Function prototypes. */
|
|
|
|
static ntfs_inode *ntfs_inode_open_from_path_reparse(ntfs_vd *vd, const char *path, int reparse_depth);
|
|
|
|
static void ntfs_split_path(const char *path, ntfs_path *p);
|
|
|
|
#ifdef DEBUG
|
|
int ntfs_log_handler_usbhsfs(const char *function, const char *file, int line, u32 level, void *data, const char *format, va_list args)
|
|
{
|
|
(void)data;
|
|
|
|
int ret = 0;
|
|
size_t formatted_str_len = 0;
|
|
char *formatted_str = NULL;
|
|
|
|
/* Get formatted string length. */
|
|
formatted_str_len = vsnprintf(NULL, 0, format, args);
|
|
if (!formatted_str_len) return ret;
|
|
|
|
/* Allocate buffer for the formatted string. */
|
|
formatted_str = calloc(++formatted_str_len, sizeof(char));
|
|
if (!formatted_str) return ret;
|
|
|
|
/* Generate formatted string and save it to the logfile. */
|
|
ret = (int)vsnprintf(formatted_str, formatted_str_len, format, args);
|
|
if (ret)
|
|
{
|
|
/* Remove CRLFs and dots - we take care of them. */
|
|
if (formatted_str[formatted_str_len - 1] == '\n') formatted_str[--formatted_str_len] = '\0';
|
|
if (formatted_str[formatted_str_len - 1] == '\r') formatted_str[--formatted_str_len] = '\0';
|
|
if (formatted_str[formatted_str_len - 1] == '.') formatted_str[--formatted_str_len] = '\0';
|
|
|
|
/* Log message. */
|
|
usbHsFsLogWriteFormattedStringToLogFile(function, "%s (file \"%s\", line %d, level 0x%X).", formatted_str, file, line, level);
|
|
}
|
|
|
|
/* Free allocated buffer. */
|
|
free(formatted_str);
|
|
|
|
return ret;
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
ntfs_inode *ntfs_inode_open_from_path(ntfs_vd *vd, const char *path)
|
|
{
|
|
return ntfs_inode_open_from_path_reparse(vd, path, 1);
|
|
}
|
|
|
|
ntfs_inode *ntfs_inode_create(ntfs_vd *vd, const char *path, mode_t type, const char *target)
|
|
{
|
|
ntfs_path full_path = {0};
|
|
ntfs_inode *dir_ni = NULL, *ni = NULL;
|
|
ntfschar *uname = NULL, *utarget = NULL;
|
|
int uname_len = 0, utarget_len = 0;
|
|
|
|
/* Safety check. */
|
|
if (!vd || !vd->vol || !path || !*path || (type == S_IFLNK && (!target || !*target)))
|
|
{
|
|
errno = EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
/* Split entry path. */
|
|
ntfs_split_path(path, &full_path);
|
|
|
|
/* Make sure we have a valid entry name to work with. */
|
|
if (full_path.name[0] == '\0')
|
|
{
|
|
errno = EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
/* Open the parent directory the desired entry will be created in. */
|
|
dir_ni = ntfs_inode_open_from_path(vd, full_path.dir);
|
|
if (!dir_ni) goto end;
|
|
|
|
/* Convert the entry name string from our current locale (UTF-8) into UTF-16LE. */
|
|
uname_len = ntfs_mbstoucs(full_path.name, &uname);
|
|
if (uname_len <= 0)
|
|
{
|
|
errno = EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
/* Create the new entry. */
|
|
switch(type)
|
|
{
|
|
case S_IFDIR: /* Directory. */
|
|
case S_IFREG: /* File. */
|
|
USBHSFS_LOG_MSG("Creating inode in directory \"%s\" named \"%s\".", full_path.dir, full_path.name);
|
|
ni = ntfs_create(dir_ni, 0, uname, uname_len, type);
|
|
break;
|
|
case S_IFLNK: /* Symbolic link. */
|
|
/* Convert the target link path string from our current locale (UTF-8) into UTF-16LE. */
|
|
utarget_len = ntfs_mbstoucs(target, &utarget);
|
|
if (utarget_len <= 0)
|
|
{
|
|
errno = EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
USBHSFS_LOG_MSG("Creating symlink in directory \"%s\" named \"%s\" targetting \"%s\".", full_path.dir, full_path.name, target);
|
|
ni = ntfs_create_symlink(dir_ni, 0, uname, uname_len, utarget, utarget_len);
|
|
break;
|
|
default: /* Invalid entry. */
|
|
errno = EINVAL;
|
|
break;
|
|
}
|
|
|
|
end:
|
|
if (utarget) free(utarget);
|
|
|
|
if (uname) free(uname);
|
|
|
|
if (dir_ni) ntfs_inode_close(dir_ni);
|
|
|
|
return ni;
|
|
}
|
|
|
|
int ntfs_inode_link(ntfs_vd *vd, const char *old_path, const char *new_path)
|
|
{
|
|
ntfs_path full_old_path = {0}, full_new_path = {0};
|
|
ntfs_inode *ni = NULL, *dir_ni = NULL;
|
|
ntfschar *uname = NULL;
|
|
int ret = -1, uname_len = 0;
|
|
|
|
/* Safety check. */
|
|
if (!vd || !vd->vol || !old_path || !*old_path || !new_path || !*new_path)
|
|
{
|
|
errno = EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
/* Split entry paths. */
|
|
ntfs_split_path(old_path, &full_old_path);
|
|
ntfs_split_path(new_path, &full_new_path);
|
|
|
|
/* Make sure we have valid old and new entry names to work with. */
|
|
if (full_old_path.name[0] == '\0' || full_new_path.name[0] == '\0')
|
|
{
|
|
errno = EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
/* Open the entry we will create a symlink for. */
|
|
ni = ntfs_inode_open_from_path(vd, full_old_path.path);
|
|
if (!ni) goto end;
|
|
|
|
/* Open new parent directory. */
|
|
dir_ni = ntfs_inode_open_from_path(vd, full_new_path.dir);
|
|
if (!dir_ni) goto end;
|
|
|
|
/* Convert the entry name string from our current locale (UTF-8) into UTF-16LE. */
|
|
uname_len = ntfs_mbstoucs(full_new_path.name, &uname);
|
|
if (uname_len <= 0)
|
|
{
|
|
errno = EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
/* Link the entry to its new parent directory. */
|
|
USBHSFS_LOG_MSG("Linking inode \"%s\" to \"%s\".", full_old_path.path, full_new_path.path);
|
|
ret = ntfs_link(ni, dir_ni, uname, uname_len);
|
|
|
|
end:
|
|
if (uname) free(uname);
|
|
|
|
if (dir_ni) ntfs_inode_close(dir_ni);
|
|
|
|
if (ni) ntfs_inode_close(ni);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ntfs_inode_unlink(ntfs_vd *vd, const char *path)
|
|
{
|
|
ntfs_path full_path = {0};
|
|
ntfs_inode *ni = NULL, *dir_ni = NULL;
|
|
ntfschar *uname = NULL;
|
|
int ret = -1, uname_len = 0;
|
|
|
|
/* Safety check. */
|
|
if (!vd || !vd->vol || !path || !*path)
|
|
{
|
|
errno = EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
/* Split entry path. */
|
|
ntfs_split_path(path, &full_path);
|
|
|
|
/* Make sure we have a valid entry name to work with. */
|
|
if (full_path.name[0] == '\0')
|
|
{
|
|
errno = EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
/* Open entry. */
|
|
ni = ntfs_inode_open_from_path(vd, full_path.path);
|
|
if (!ni) goto end;
|
|
|
|
/* Open parent directory. */
|
|
dir_ni = ntfs_inode_open_from_path(vd, full_path.dir);
|
|
if (!dir_ni) goto end;
|
|
|
|
/* Convert the entry name string from our current locale (UTF-8) into UTF-16LE. */
|
|
uname_len = ntfs_mbstoucs(full_path.name, &uname);
|
|
if (uname_len <= 0)
|
|
{
|
|
errno = EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
USBHSFS_LOG_MSG("Unlinking inode \"%s\" from \"%s\".", full_path.name, full_path.dir);
|
|
|
|
/* Unlink entry from its parent. */
|
|
/* 'ni' and 'dir_ni' are always closed by ntfs_delete(), even if it fails. */
|
|
ret = ntfs_delete(vd->vol, full_path.path, ni, dir_ni, uname, uname_len);
|
|
|
|
ni = NULL;
|
|
dir_ni = NULL;
|
|
|
|
end:
|
|
if (uname) free(uname);
|
|
|
|
if (dir_ni) ntfs_inode_close(dir_ni);
|
|
|
|
if (ni) ntfs_inode_close(ni);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void ntfs_inode_update_times_filtered(ntfs_vd *vd, ntfs_inode *ni, ntfs_time_update_flags mask)
|
|
{
|
|
if (!vd || !ni) return;
|
|
|
|
/* Run the access time update strategy against the volume settings first. */
|
|
if (!vd->update_access_times) mask &= ~NTFS_UPDATE_ATIME;
|
|
|
|
/* Update entry times. */
|
|
if (mask)
|
|
{
|
|
USBHSFS_LOG_MSG("Updating access times for inode %lu (mask 0x%X).", ni->mft_no, mask);
|
|
ntfs_inode_update_times(ni, mask);
|
|
}
|
|
}
|
|
|
|
static ntfs_inode *ntfs_inode_open_from_path_reparse(ntfs_vd *vd, const char *path, int reparse_depth)
|
|
{
|
|
ntfs_inode *ni = NULL;
|
|
char *target = NULL;
|
|
|
|
/* Safety check. */
|
|
if (!vd || !vd->vol || !path || !*path || reparse_depth <= 0 || reparse_depth > NTFS_MAX_SYMLINK_DEPTH)
|
|
{
|
|
errno = EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
USBHSFS_LOG_MSG("Opening requested inode \"%s\" (reparse depth %d).", path, reparse_depth);
|
|
|
|
/* Open requested inode. */
|
|
ni = ntfs_pathname_to_inode(vd->vol, NULL, path);
|
|
if (!ni)
|
|
{
|
|
USBHSFS_LOG_MSG("Failed to open requested inode \"%s\" (errno %d).", path, errno);
|
|
goto end;
|
|
}
|
|
|
|
USBHSFS_LOG_MSG("Successfully opened inode from path \"%s\" (mft_no %lu).", path, ni->mft_no);
|
|
|
|
/* If the entry was found and it has reparse data, then resolve the true entry. */
|
|
/* This effectively follows directory junctions and symbolic links until the target entry is found. */
|
|
if ((ni->flags & FILE_ATTR_REPARSE_POINT) && ntfs_possible_symlink(ni))
|
|
{
|
|
/* Get the target path of this entry. */
|
|
target = ntfs_make_symlink(ni, path);
|
|
if (!target) goto end;
|
|
|
|
/* Close this entry (we are no longer interested in it). */
|
|
ntfs_inode_close(ni);
|
|
|
|
/* Open the target entry. */
|
|
USBHSFS_LOG_MSG("Following inode symlink \"%s\" -> \"%s\".", path, target);
|
|
ni = ntfs_inode_open_from_path_reparse(vd, target, ++reparse_depth);
|
|
|
|
/* Clean up. */
|
|
free(target);
|
|
}
|
|
|
|
end:
|
|
return ni;
|
|
}
|
|
|
|
/* This function doesn't perform checks on the provided path because it is guaranteed to be valid. */
|
|
/* Check ntfsdev_fixpath(). */
|
|
static void ntfs_split_path(const char *path, ntfs_path *p)
|
|
{
|
|
USBHSFS_LOG_MSG("Input path: \"%s\".", path);
|
|
|
|
/* Setup NTFS path. */
|
|
memset(p, 0, sizeof(ntfs_path));
|
|
p->path = path;
|
|
|
|
/* Copy the path to internal buffer so we can modify it. */
|
|
strcpy(p->buf, p->path);
|
|
|
|
/* Split the path into separate directory and filename parts. */
|
|
/* e.g. "/dir/file.txt" => dir: "/dir", name: "file.txt". */
|
|
char *buf_sep = strrchr(p->buf, PATH_SEP);
|
|
|
|
/* If there's just a single path separator at the start of the string, set the directory string to a path separator. */
|
|
/* Otherwise, just use the directory string as-is. */
|
|
p->dir = (buf_sep == p->buf ? "/" : p->buf);
|
|
|
|
/* Remove the path separator we found and update the entry name pointer. */
|
|
*buf_sep = '\0';
|
|
p->name = (buf_sep + 1);
|
|
|
|
USBHSFS_LOG_MSG("Output strings -> Path: \"%s\" | Directory: \"%s\" | Name: \"%s\".", p->path, p->dir, p->name);
|
|
}
|