* Copyright 2021, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Augustin Cavalier <waddlesplash>
*/
#include <dirent.h>
#include <unistd.h>
#include <util/kernel_cpp.h>
#include <string.h>
#include <AutoDeleter.h>
#include <fs_cache.h>
#include <fs_info.h>
#include <vfs.h>
#include <NodeMonitor.h>
#include <file_systems/DeviceOpener.h>
#include <file_systems/fs_ops_support.h>
#include <util/AutoLock.h>
#include "ntfs.h"
extern "C" {
#include "libntfs/bootsect.h"
#include "libntfs/dir.h"
#include "utils/utils.h"
}
#ifdef TRACE_NTFS
# define TRACE(X...) dprintf("ntfs: " X)
#else
# define TRACE(X...) ;
#endif
#define CALLED() TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
#define ERROR(X...) dprintf("ntfs: error: " X)
struct identify_cookie {
NTFS_BOOT_SECTOR boot;
};
extern "C" int mkntfs_main(const char* devpath, const char* label);
typedef CObjectDeleter<ntfs_inode, int, ntfs_inode_close> NtfsInodeCloser;
typedef CObjectDeleter<ntfs_attr, void, ntfs_attr_close> NtfsAttrCloser;
static status_t fs_access(fs_volume* _volume, fs_vnode* _node, int accessMode);
static float
fs_identify_partition(int fd, partition_data* partition, void** _cookie)
{
CALLED();
NTFS_BOOT_SECTOR boot;
if (read_pos(fd, 0, (void*)&boot, 512) != 512) {
ERROR("identify_partition: failed to read boot sector\n");
return -1;
}
if (strncmp(((const char*)&boot) + 3, "NTFS", 4) != 0)
return -1;
if (!ntfs_boot_sector_is_ntfs(&boot)) {
ERROR("identify_partition: boot signature invalid\n");
return -1;
}
identify_cookie* cookie = new identify_cookie;
if (cookie == NULL) {
ERROR("identify_partition: cookie allocation failed\n");
return -1;
}
memcpy(&cookie->boot, &boot, sizeof(boot));
*_cookie = cookie;
return 0.82f;
}
static status_t
fs_scan_partition(int fd, partition_data* partition, void* _cookie)
{
CALLED();
identify_cookie *cookie = (identify_cookie*)_cookie;
partition->status = B_PARTITION_VALID;
partition->flags |= B_PARTITION_FILE_SYSTEM;
partition->content_size = sle64_to_cpu(cookie->boot.number_of_sectors)
* le16_to_cpu(cookie->boot.bpb.bytes_per_sector);
partition->block_size = le16_to_cpu(cookie->boot.bpb.bytes_per_sector);
ntfs_volume* ntVolume;
char path[B_PATH_NAME_LENGTH];
if (ioctl(fd, B_GET_PATH_FOR_DEVICE, path) != 0) {
ntVolume = utils_mount_volume(path, NTFS_MNT_RDONLY | NTFS_MNT_RECOVER);
if (ntVolume == NULL)
return errno ? errno : B_ERROR;
if (ntVolume->vol_name != NULL && ntVolume->vol_name[0] != '\0')
partition->content_name = strdup(ntVolume->vol_name);
else
partition->content_name = strdup("");
ntfs_umount(ntVolume, true);
}
return partition->content_name != NULL ? B_OK : B_NO_MEMORY;
}
static void
fs_free_identify_partition_cookie(partition_data* partition, void* _cookie)
{
CALLED();
delete (identify_cookie*)_cookie;
}
static status_t
fs_initialize(int fd, partition_id partitionID, const char* name,
const char* parameterString, off_t partitionSize, disk_job_id job)
{
TRACE("fs_initialize: '%s', %s\n", name, parameterString);
update_disk_device_job_progress(job, 0);
char path[B_PATH_NAME_LENGTH];
if (ioctl(fd, B_GET_PATH_FOR_DEVICE, path) == 0)
return B_BAD_VALUE;
status_t result = mkntfs_main(path, name);
if (result != 0)
return result;
result = scan_partition(partitionID);
if (result != B_OK)
return result;
update_disk_device_job_progress(job, 1);
return B_OK;
}
static status_t
fs_mount(fs_volume* _volume, const char* device, uint32 flags,
const char* args, ino_t* _rootID)
{
CALLED();
volume* volume = new struct volume;
vnode* root = new vnode;
if (volume == NULL || root == NULL)
return B_NO_MEMORY;
ObjectDeleter<struct volume> volumeDeleter(volume);
mutex_init(&volume->lock, "NTFS volume lock");
volume->fs_info_flags = B_FS_IS_PERSISTENT;
unsigned long ntfsFlags = NTFS_MNT_RECOVER | NTFS_MNT_MAY_RDONLY;
if ((flags & B_MOUNT_READ_ONLY) != 0 || DeviceOpener(device, O_RDWR).IsReadOnly())
ntfsFlags |= NTFS_MNT_RDONLY;
volume->ntfs = utils_mount_volume(device, ntfsFlags);
if (volume->ntfs == NULL)
return errno;
if (NVolReadOnly(volume->ntfs)) {
if ((ntfsFlags & NTFS_MNT_RDONLY) == 0)
ERROR("volume is hibernated, mounted as read-only\n");
volume->fs_info_flags |= B_FS_IS_READONLY;
}
if (ntfs_volume_get_free_space(volume->ntfs) != 0) {
ntfs_umount(volume->ntfs, true);
return B_ERROR;
}
const bool showSystem = false, showHidden = true, hideDot = false;
if (ntfs_set_shown_files(volume->ntfs, showSystem, showHidden, hideDot) != 0) {
ntfs_umount(volume->ntfs, true);
return B_ERROR;
}
dev_t deviceID;
ino_t nodeID;
status_t status = vfs_get_mount_point(_volume->id, &deviceID, &nodeID);
char* mountpoint;
if (status == B_OK) {
mountpoint = (char*)malloc(PATH_MAX);
status = vfs_entry_ref_to_path(deviceID, nodeID, NULL, true,
mountpoint, PATH_MAX);
if (status == B_OK) {
char* reallocated = (char*)realloc(mountpoint, strlen(mountpoint) + 1);
if (reallocated != NULL)
mountpoint = reallocated;
} else {
free(mountpoint);
mountpoint = NULL;
}
}
if (status != B_OK)
mountpoint = strdup("");
volume->lowntfs.haiku_fs_volume = _volume;
volume->lowntfs.current_close_state_vnode = NULL;
volume->lowntfs.vol = volume->ntfs;
volume->ntfs->abs_mnt_point = volume->lowntfs.abs_mnt_point = mountpoint;
volume->lowntfs.dmask = 0;
volume->lowntfs.fmask = S_IXUSR | S_IXGRP | S_IXOTH;
volume->lowntfs.dmtime = 0;
volume->lowntfs.special_files = NTFS_FILES_INTERIX;
volume->lowntfs.posix_nlink = 0;
volume->lowntfs.inherit = 0;
volume->lowntfs.windows_names = 1;
volume->lowntfs.latest_ghost = 0;
*_rootID = root->inode = FILE_root;
root->parent_inode = (u64)-1;
root->mode = S_IFDIR | ACCESSPERMS;
root->uid = root->gid = 0;
root->size = 0;
status = publish_vnode(_volume, root->inode, root, &gNtfsVnodeOps, S_IFDIR, 0);
if (status != B_OK) {
ntfs_umount(volume->ntfs, true);
return status;
}
volumeDeleter.Detach();
_volume->private_volume = volume;
_volume->ops = &gNtfsVolumeOps;
return B_OK;
}
static status_t
fs_unmount(fs_volume* _volume)
{
CALLED();
volume* volume = (struct volume*)_volume->private_volume;
if (ntfs_umount(volume->ntfs, false) < 0)
return errno;
put_vnode(_volume, FILE_root);
delete volume;
_volume->private_volume = NULL;
return B_OK;
}
static status_t
fs_read_fs_info(fs_volume* _volume, struct fs_info* info)
{
CALLED();
volume* volume = (struct volume*)_volume->private_volume;
MutexLocker lock(volume->lock);
info->flags = volume->fs_info_flags;
info->block_size = volume->ntfs->cluster_size;
info->total_blocks = volume->ntfs->nr_clusters;
info->free_blocks = volume->ntfs->free_clusters;
info->io_size = 65536;
strlcpy(info->volume_name, volume->ntfs->vol_name, sizeof(info->volume_name));
strlcpy(info->fsh_name, "NTFS", sizeof(info->fsh_name));
return B_OK;
}
static status_t
fs_write_fs_info(fs_volume* _volume, const struct fs_info* info, uint32 mask)
{
CALLED();
volume* volume = (struct volume*)_volume->private_volume;
MutexLocker lock(volume->lock);
if ((volume->fs_info_flags & B_FS_IS_READONLY) != 0)
return B_READ_ONLY_DEVICE;
status_t status = B_OK;
if ((mask & FS_WRITE_FSINFO_NAME) != 0) {
ntfschar* label = NULL;
int label_len = ntfs_mbstoucs(info->volume_name, &label);
if (label_len <= 0 || label == NULL)
return -1;
MemoryDeleter nameDeleter(label);
if (ntfs_volume_rename(volume->ntfs, label, label_len) != 0)
status = errno;
}
return status;
}
static status_t
fs_init_vnode(fs_volume* _volume, ino_t parent, ino_t nid, vnode** _vnode, bool publish = false)
{
volume* volume = (struct volume*)_volume->private_volume;
ASSERT_LOCKED_MUTEX(&volume->lock);
ntfs_inode* ni = ntfs_inode_open(volume->ntfs, nid);
if (ni == NULL)
return ENOENT;
NtfsInodeCloser niCloser(ni);
vnode* node = new vnode;
if (node == NULL)
return B_NO_MEMORY;
ObjectDeleter<vnode> vnodeDeleter(node);
struct stat statbuf;
if (ntfs_fuse_getstat(&volume->lowntfs, NULL, ni, &statbuf) != 0)
return errno;
node->inode = nid;
node->parent_inode = parent;
node->uid = statbuf.st_uid;
node->gid = statbuf.st_gid;
node->mode = statbuf.st_mode;
node->size = statbuf.st_size;
char path[B_FILE_NAME_LENGTH];
if (utils_inode_get_name(ni, path, sizeof(path)) == 0)
return B_NO_MEMORY;
node->name = strdup(strrchr(path, '/') + 1);
if (publish) {
status_t status = publish_vnode(_volume, node->inode, node, &gNtfsVnodeOps, node->mode, 0);
if (status != B_OK)
return status;
}
if ((node->mode & S_IFDIR) == 0) {
node->file_cache = file_cache_create(_volume->id, nid, node->size);
if (node->file_cache == NULL)
return B_NO_INIT;
}
vnodeDeleter.Detach();
*_vnode = node;
return B_OK;
}
static status_t
fs_get_vnode(fs_volume* _volume, ino_t nid, fs_vnode* _node, int* _type,
uint32* _flags, bool reenter)
{
TRACE("get_vnode %" B_PRIdINO "\n", nid);
volume* volume = (struct volume*)_volume->private_volume;
MutexLocker lock(reenter ? NULL : &volume->lock);
vnode* vnode;
status_t status = fs_init_vnode(_volume, -1 , nid, &vnode);
if (status != B_OK)
return status;
_node->private_node = vnode;
_node->ops = &gNtfsVnodeOps;
*_type = vnode->mode;
*_flags = 0;
return B_OK;
}
static status_t
fs_put_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter)
{
CALLED();
volume* volume = (struct volume*)_volume->private_volume;
MutexLocker lock(reenter ? NULL : &volume->lock);
vnode* node = (vnode*)_node->private_node;
file_cache_delete(node->file_cache);
delete node;
return B_OK;
}
static status_t
fs_remove_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter)
{
CALLED();
volume* volume = (struct volume*)_volume->private_volume;
MutexLocker lock(reenter ? NULL : &volume->lock);
vnode* node = (vnode*)_node->private_node;
if (ntfs_fuse_release(&volume->lowntfs, node->parent_inode, node->inode,
node->lowntfs_close_state, node->lowntfs_ghost) != 0)
return errno;
file_cache_delete(node->file_cache);
delete node;
return B_OK;
}
int*
ntfs_haiku_get_close_state(struct lowntfs_context *ctx, u64 ino)
{
if (ctx->current_close_state_vnode != NULL)
panic("NTFS current_close_state_vnode should be NULL!");
vnode* node = NULL;
if (get_vnode((fs_volume*)ctx->haiku_fs_volume, ino, (void**)&node) != B_OK)
return NULL;
ctx->current_close_state_vnode = node;
return &node->lowntfs_close_state;
}
void
ntfs_haiku_put_close_state(struct lowntfs_context *ctx, u64 ino, u64 ghost)
{
vnode* node = (vnode*)ctx->current_close_state_vnode;
if (node == NULL)
return;
node->lowntfs_ghost = ghost;
if ((node->lowntfs_close_state & CLOSE_GHOST) != 0) {
fs_volume* _volume = (fs_volume*)ctx->haiku_fs_volume;
entry_cache_remove(_volume->id, node->parent_inode, node->name);
notify_entry_removed(_volume->id, node->parent_inode, node->name, node->inode);
remove_vnode(_volume, node->inode);
}
ctx->current_close_state_vnode = NULL;
put_vnode((fs_volume*)ctx->haiku_fs_volume, node->inode);
}
static bool
fs_can_page(fs_volume* _volume, fs_vnode* _node, void* _cookie)
{
return true;
}
static status_t
fs_read_pages(fs_volume* _volume, fs_vnode* _node, void* _cookie,
off_t pos, const iovec* vecs, size_t count, size_t* _numBytes)
{
volume* volume = (struct volume*)_volume->private_volume;
MutexLocker lock(volume->lock);
vnode* node = (vnode*)_node->private_node;
TRACE("read_pages inode: %" B_PRIdINO", pos: %" B_PRIdOFF "; vecs: %p; "
"count: %" B_PRIuSIZE "; numBytes: %" B_PRIuSIZE "\n", node->inode, pos,
vecs, count, *_numBytes);
ntfs_inode* ni = ntfs_inode_open(volume->ntfs, node->inode);
if (ni == NULL)
return B_IO_ERROR;
NtfsInodeCloser niCloser(ni);
if (pos < 0 || pos >= ni->data_size)
return B_BAD_VALUE;
size_t bytesLeft = min_c(*_numBytes, size_t(ni->data_size - pos));
*_numBytes = 0;
for (size_t i = 0; i < count && bytesLeft > 0; i++) {
const size_t ioSize = min_c(bytesLeft, vecs[i].iov_len);
const int read = ntfs_fuse_read(ni, pos, (char*)vecs[i].iov_base, ioSize);
if (read < 0)
return errno;
pos += read;
*_numBytes += read;
bytesLeft -= read;
if (size_t(read) != ioSize)
return errno;
}
return B_OK;
}
static status_t
fs_write_pages(fs_volume* _volume, fs_vnode* _node, void* _cookie,
off_t pos, const iovec* vecs, size_t count, size_t* _numBytes)
{
volume* volume = (struct volume*)_volume->private_volume;
MutexLocker lock(volume->lock);
vnode* node = (vnode*)_node->private_node;
TRACE("write_pages inode: %" B_PRIdINO", pos: %" B_PRIdOFF "; vecs: %p; "
"count: %" B_PRIuSIZE "; numBytes: %" B_PRIuSIZE "\n", node->inode, pos,
vecs, count, *_numBytes);
ntfs_inode* ni = ntfs_inode_open(volume->ntfs, node->inode);
if (ni == NULL)
return B_IO_ERROR;
NtfsInodeCloser niCloser(ni);
if (pos < 0 || pos >= ni->data_size)
return B_BAD_VALUE;
size_t bytesLeft = min_c(*_numBytes, size_t(ni->data_size - pos));
*_numBytes = 0;
for (size_t i = 0; i < count && bytesLeft > 0; i++) {
const size_t ioSize = min_c(bytesLeft, vecs[i].iov_len);
const int written = ntfs_fuse_write(&volume->lowntfs, ni, (char*)vecs[i].iov_base, ioSize, pos);
if (written < 0)
return errno;
pos += written;
*_numBytes += written;
bytesLeft -= written;
if (size_t(written) != ioSize)
return errno;
}
return B_OK;
}
static status_t
fs_lookup(fs_volume* _volume, fs_vnode* _directory, const char* name,
ino_t* _vnodeID)
{
TRACE("fs_lookup: name address: %p (%s)\n", name, name);
volume* volume = (struct volume*)_volume->private_volume;
MutexLocker lock(volume->lock);
vnode* directory = (vnode*)_directory->private_node;
status_t result;
if (strcmp(name, ".") == 0) {
*_vnodeID = directory->inode;
} else if (strcmp(name, "..") == 0) {
if (directory->inode == FILE_root)
return ENOENT;
*_vnodeID = directory->parent_inode;
} else {
u64 inode = ntfs_fuse_inode_lookup(&volume->lowntfs, directory->inode, name);
if (inode == (u64)-1)
return errno;
*_vnodeID = inode;
}
result = entry_cache_add(_volume->id, directory->inode, name, *_vnodeID);
if (result != B_OK)
return result;
vnode* node = NULL;
result = get_vnode(_volume, *_vnodeID, (void**)&node);
if (result != B_OK)
return result;
if (node->parent_inode == (u64)-1)
node->parent_inode = directory->inode;
TRACE("fs_lookup: ID %" B_PRIdINO "\n", *_vnodeID);
return B_OK;
}
static status_t
fs_get_vnode_name(fs_volume* _volume, fs_vnode* _node, char* buffer, size_t bufferSize)
{
vnode* node = (vnode*)_node->private_node;
if (strlcpy(buffer, node->name, bufferSize) >= bufferSize)
return B_BUFFER_OVERFLOW;
return B_OK;
}
static status_t
fs_ioctl(fs_volume* _volume, fs_vnode* _node, void* _cookie, uint32 cmd,
void* buffer, size_t bufferLength)
{
return B_DEV_INVALID_IOCTL;
}
static status_t
fs_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* stat)
{
CALLED();
volume* volume = (struct volume*)_volume->private_volume;
MutexLocker lock(volume->lock);
vnode* node = (vnode*)_node->private_node;
ntfs_inode* ni = ntfs_inode_open(volume->ntfs, node->inode);
if (ni == NULL)
return errno;
NtfsInodeCloser niCloser(ni);
if (ntfs_fuse_getstat(&volume->lowntfs, NULL, ni, stat) != 0)
return errno;
return B_OK;
}
static status_t
fs_write_stat(fs_volume* _volume, fs_vnode* _node, const struct stat* stat, uint32 mask)
{
CALLED();
volume* volume = (struct volume*)_volume->private_volume;
MutexLocker lock(volume->lock);
vnode* node = (vnode*)_node->private_node;
if ((volume->fs_info_flags & B_FS_IS_READONLY) != 0)
return B_READ_ONLY_DEVICE;
ntfs_inode* ni = ntfs_inode_open(volume->ntfs, node->inode);
if (ni == NULL)
return B_IO_ERROR;
NtfsInodeCloser niCloser(ni);
bool updateTime = false;
const uid_t euid = geteuid();
const bool isOwnerOrRoot = (euid == 0 || euid == (uid_t)node->uid);
const bool hasWriteAccess = fs_access(_volume, _node, W_OK);
if ((mask & B_STAT_SIZE) != 0 && node->size != stat->st_size) {
if ((node->mode & S_IFDIR) != 0)
return B_IS_A_DIRECTORY;
if (!hasWriteAccess)
return B_NOT_ALLOWED;
ntfs_attr* na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
if (na == NULL)
return errno;
NtfsAttrCloser naCloser(na);
if (ntfs_attr_truncate(na, stat->st_size) != 0)
return errno;
node->size = na->data_size;
file_cache_set_size(node->file_cache, node->size);
updateTime = true;
}
if ((mask & B_STAT_UID) != 0) {
if (euid != 0)
return B_NOT_ALLOWED;
if (node->uid != stat->st_uid)
return B_UNSUPPORTED;
}
if ((mask & B_STAT_GID) != 0) {
if (!isOwnerOrRoot)
return B_NOT_ALLOWED;
if (node->gid != stat->st_gid)
return B_UNSUPPORTED;
}
if ((mask & B_STAT_MODE) != 0) {
if (!isOwnerOrRoot)
return B_NOT_ALLOWED;
if (node->mode != stat->st_mode)
return B_UNSUPPORTED;
}
if ((mask & B_STAT_CREATION_TIME) != 0) {
if (!isOwnerOrRoot && !hasWriteAccess)
return B_NOT_ALLOWED;
ni->creation_time = timespec2ntfs(stat->st_crtim);
}
if ((mask & B_STAT_MODIFICATION_TIME) != 0) {
if (!isOwnerOrRoot && !hasWriteAccess)
return B_NOT_ALLOWED;
ni->last_data_change_time = timespec2ntfs(stat->st_mtim);
}
if ((mask & B_STAT_CHANGE_TIME) != 0 || updateTime) {
if (!isOwnerOrRoot && !hasWriteAccess)
return B_NOT_ALLOWED;
ni->last_mft_change_time = timespec2ntfs(stat->st_ctim);
}
if ((mask & B_STAT_ACCESS_TIME) != 0) {
if (!isOwnerOrRoot && !hasWriteAccess)
return B_NOT_ALLOWED;
ni->last_access_time = timespec2ntfs(stat->st_atim);
}
ntfs_inode_mark_dirty(ni);
notify_stat_changed(_volume->id, node->parent_inode, node->inode, mask);
return B_OK;
}
static status_t
fs_generic_create(fs_volume* _volume, vnode* directory, const char* name, int mode,
ino_t* _inode)
{
volume* volume = (struct volume*)_volume->private_volume;
ASSERT_LOCKED_MUTEX(&volume->lock);
if ((directory->mode & S_IFDIR) == 0)
return B_BAD_TYPE;
ino_t inode = -1;
if (ntfs_fuse_create(&volume->lowntfs, directory->inode, name, mode & (S_IFMT | 07777),
0, (char*)NULL, &inode) != 0)
return errno;
vnode* node;
status_t status = fs_init_vnode(_volume, directory->inode, inode, &node, true);
if (status != B_OK)
return status;
entry_cache_add(_volume->id, directory->inode, name, inode);
notify_entry_created(_volume->id, directory->inode, name, inode);
*_inode = inode;
return B_OK;
}
static status_t
fs_create(fs_volume* _volume, fs_vnode* _directory, const char* name,
int openMode, int mode, void** _cookie, ino_t* _vnodeID)
{
CALLED();
volume* volume = (struct volume*)_volume->private_volume;
MutexLocker lock(volume->lock);
vnode* directory = (vnode*)_directory->private_node;
if ((directory->mode & S_IFDIR) == 0)
return B_NOT_A_DIRECTORY;
status_t status = fs_access(_volume, _directory, W_OK);
if (status != B_OK)
return status;
#if 1
if ((openMode & O_NOCACHE) != 0)
return B_UNSUPPORTED;
#endif
status = fs_generic_create(_volume, directory, name, S_IFREG | (mode & 07777), _vnodeID);
if (status != B_OK)
return status;
file_cookie* cookie = new file_cookie;
if (cookie == NULL)
return B_NO_MEMORY;
ObjectDeleter<file_cookie> cookieDeleter(cookie);
cookie->open_mode = openMode;
cookieDeleter.Detach();
*_cookie = cookie;
return B_OK;
}
static status_t
fs_open(fs_volume* _volume, fs_vnode* _node, int openMode, void** _cookie)
{
CALLED();
volume* volume = (struct volume*)_volume->private_volume;
MutexLocker lock(volume->lock);
vnode* node = (vnode*)_node->private_node;
if ((node->mode & S_IFDIR) != 0 && (openMode & O_RWMASK) != 0)
return B_IS_A_DIRECTORY;
if ((openMode & O_DIRECTORY) != 0 && (node->mode & S_IFDIR) == 0)
return B_NOT_A_DIRECTORY;
status_t status = fs_access(_volume, _node, open_mode_to_access(openMode));
if (status != B_OK)
return status;
ntfs_inode* ni = ntfs_inode_open(volume->ntfs, node->inode);
if (ni == NULL)
return errno;
NtfsInodeCloser niCloser(ni);
file_cookie* cookie = new file_cookie;
if (cookie == NULL)
return B_NO_MEMORY;
ObjectDeleter<file_cookie> cookieDeleter(cookie);
cookie->open_mode = openMode;
#if 0
if ((openMode & O_NOCACHE) != 0 && node->file_cache != NULL) {
status_t status = file_cache_disable(node->file_cache);
if (status != B_OK)
return status;
}
#else
if ((openMode & O_NOCACHE) != 0)
return B_UNSUPPORTED;
#endif
if ((openMode & O_TRUNC) != 0) {
if ((openMode & O_RWMASK) == O_RDONLY)
return B_NOT_ALLOWED;
ntfs_attr* na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
if (na == NULL)
return errno;
NtfsAttrCloser naCloser(na);
if (ntfs_attr_truncate(na, 0) != 0)
return errno;
node->size = na->data_size;
file_cache_set_size(node->file_cache, node->size);
}
cookieDeleter.Detach();
*_cookie = cookie;
return B_OK;
}
static status_t
fs_read(fs_volume* _volume, fs_vnode* _node, void* _cookie, off_t pos,
void* buffer, size_t* length)
{
vnode* node = (vnode*)_node->private_node;
file_cookie* cookie = (file_cookie*)_cookie;
if ((node->mode & S_IFDIR) != 0)
return B_IS_A_DIRECTORY;
ASSERT((cookie->open_mode & O_RWMASK) == O_RDONLY || (cookie->open_mode & O_RDWR) != 0);
return file_cache_read(node->file_cache, cookie, pos, buffer, length);
}
static status_t
fs_write(fs_volume* _volume, fs_vnode* _node, void* _cookie, off_t pos,
const void* buffer, size_t* _length)
{
CALLED();
volume* volume = (struct volume*)_volume->private_volume;
MutexLocker lock(volume->lock);
vnode* node = (vnode*)_node->private_node;
file_cookie* cookie = (file_cookie*)_cookie;
if ((node->mode & S_IFDIR) != 0)
return B_IS_A_DIRECTORY;
ASSERT((cookie->open_mode & O_WRONLY) != 0 || (cookie->open_mode & O_RDWR) != 0);
if (cookie->open_mode & O_APPEND)
pos = node->size;
if (pos < 0)
return B_BAD_VALUE;
if (pos + s64(*_length) > node->size) {
ntfs_inode* ni = ntfs_inode_open(volume->ntfs, node->inode);
if (ni == NULL)
return errno;
NtfsInodeCloser niCloser(ni);
ntfs_attr* na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
if (na == NULL)
return errno;
NtfsAttrCloser naCloser(na);
if (ntfs_attr_truncate(na, pos + *_length) != 0)
return errno;
node->size = na->data_size;
file_cache_set_size(node->file_cache, node->size);
}
lock.Unlock();
status_t status = file_cache_write(node->file_cache, cookie, pos, buffer, _length);
if (status != B_OK)
return status;
lock.Lock();
if ((node->lowntfs_close_state & CLOSE_GHOST) == 0
&& cookie->last_size != node->size
&& system_time() > cookie->last_notification + INODE_NOTIFICATION_INTERVAL) {
notify_stat_changed(_volume->id, node->parent_inode, node->inode,
B_STAT_MODIFICATION_TIME | B_STAT_SIZE | B_STAT_INTERIM_UPDATE);
cookie->last_size = node->size;
cookie->last_notification = system_time();
}
return status;
}
static status_t
fs_fsync(fs_volume* _volume, fs_vnode* _node, bool dataOnly)
{
CALLED();
vnode* node = (vnode*)_node->private_node;
return file_cache_sync(node->file_cache);
}
static status_t
fs_close(fs_volume *_volume, fs_vnode *_node, void *_cookie)
{
CALLED();
return B_OK;
}
static status_t
fs_free_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie)
{
file_cookie* cookie = (file_cookie*)_cookie;
delete cookie;
return B_OK;
}
static status_t
fs_generic_unlink(fs_volume* _volume, fs_vnode* _directory, const char* name, RM_TYPES type)
{
volume* volume = (struct volume*)_volume->private_volume;
MutexLocker lock(volume->lock);
vnode* directory = (vnode*)_directory->private_node;
status_t status = fs_access(_volume, _directory, W_OK);
if (status != B_OK)
return status;
if (ntfs_fuse_rm(&volume->lowntfs, directory->inode, name, type) != 0)
return errno;
return B_OK;
}
static status_t
fs_unlink(fs_volume* _volume, fs_vnode* _directory, const char* name)
{
CALLED();
return fs_generic_unlink(_volume, _directory, name, RM_LINK);
}
static status_t
fs_rename(fs_volume* _volume, fs_vnode* _oldDir, const char* oldName,
fs_vnode* _newDir, const char* newName)
{
CALLED();
volume* volume = (struct volume*)_volume->private_volume;
MutexLocker lock(volume->lock);
vnode* old_directory = (vnode*)_oldDir->private_node;
vnode* new_directory = (vnode*)_newDir->private_node;
if (old_directory == new_directory && strcmp(oldName, newName) == 0)
return B_OK;
status_t status = fs_access(_volume, _oldDir, W_OK);
if (status == B_OK)
status = fs_access(_volume, _newDir, W_OK);
if (status != B_OK)
return status;
if (old_directory != new_directory) {
u64 oldIno = ntfs_fuse_inode_lookup(&volume->lowntfs, old_directory->inode, oldName);
if (oldIno == (u64)-1)
return B_ENTRY_NOT_FOUND;
ino_t parent = new_directory->inode;
const ino_t root = FILE_root;
while (true) {
if (parent == oldIno)
return B_BAD_VALUE;
else if (parent == root || parent == old_directory->inode)
break;
vnode* parentNode;
if (get_vnode(_volume, parent, (void**)&parentNode) != B_OK)
return B_ERROR;
parent = parentNode->parent_inode;
put_vnode(_volume, parentNode->inode);
}
}
if (ntfs_fuse_rename(&volume->lowntfs, old_directory->inode, oldName,
new_directory->inode, newName) != 0)
return errno;
u64 ino = ntfs_fuse_inode_lookup(&volume->lowntfs, new_directory->inode, newName);
if (ino == (u64)-1)
return B_ENTRY_NOT_FOUND;
vnode* node;
status = get_vnode(_volume, ino, (void**)&node);
if (status != B_OK)
return status;
free(node->name);
node->name = strdup(newName);
node->parent_inode = new_directory->inode;
if ((node->mode & S_IFDIR) != 0)
entry_cache_add(_volume->id, ino, "..", new_directory->inode);
put_vnode(_volume, ino);
entry_cache_remove(_volume->id, old_directory->inode, oldName);
entry_cache_add(_volume->id, new_directory->inode, newName, ino);
notify_entry_moved(_volume->id, old_directory->inode, oldName,
new_directory->inode, newName, ino);
return B_OK;
}
static status_t
fs_access(fs_volume* _volume, fs_vnode* _node, int accessMode)
{
volume* volume = (struct volume*)_volume->private_volume;
vnode* node = (vnode*)_node->private_node;
if ((accessMode & W_OK) != 0 && (volume->fs_info_flags & B_FS_IS_READONLY) != 0)
return B_READ_ONLY_DEVICE;
return check_access_permissions(accessMode, node->mode, node->gid, node->uid);
}
static status_t
fs_read_link(fs_volume* _volume, fs_vnode* _node, char* buffer, size_t* bufferSize)
{
CALLED();
volume* volume = (struct volume*)_volume->private_volume;
MutexLocker lock(volume->lock);
vnode* node = (vnode*)_node->private_node;
if (ntfs_fuse_readlink(&volume->lowntfs, node->inode, buffer, bufferSize) != 0)
return errno;
return B_OK;
}
static status_t
fs_create_dir(fs_volume* _volume, fs_vnode* _directory, const char* name, int mode)
{
CALLED();
volume* volume = (struct volume*)_volume->private_volume;
MutexLocker lock(volume->lock);
vnode* directory = (vnode*)_directory->private_node;
status_t status = fs_access(_volume, _directory, W_OK);
if (status != B_OK)
return status;
ino_t inode = -1;
status = fs_generic_create(_volume, directory, name, S_IFDIR | (mode & 07777), &inode);
if (status != B_OK)
return status;
return B_OK;
}
static status_t
fs_remove_dir(fs_volume* _volume, fs_vnode* _directory, const char* name)
{
CALLED();
ino_t directory_inode = ((vnode*)_directory->private_node)->inode;
status_t status = fs_generic_unlink(_volume, _directory, name, RM_DIR);
if (status != B_OK)
return status;
entry_cache_remove(_volume->id, directory_inode, "..");
return B_OK;
}
static status_t
fs_open_dir(fs_volume* _volume, fs_vnode* _node, void** _cookie)
{
CALLED();
vnode* node = (vnode*)_node->private_node;
status_t status = fs_access(_volume, _node, R_OK);
if (status != B_OK)
return status;
if ((node->mode & S_IFDIR) == 0)
return B_NOT_A_DIRECTORY;
directory_cookie* cookie = new directory_cookie;
if (cookie == NULL)
return B_NO_MEMORY;
cookie->first = cookie->current = NULL;
*_cookie = (void*)cookie;
return B_OK;
}
static int
_ntfs_readdir_callback(void* _cookie, const ntfschar* ntfs_name, const int ntfs_name_len,
const int name_type, const s64 pos, const MFT_REF mref, const unsigned dt_type)
{
if (name_type == FILE_NAME_DOS)
return 0;
directory_cookie* cookie = (directory_cookie*)_cookie;
char* name = NULL;
int name_len = ntfs_ucstombs(ntfs_name, ntfs_name_len, &name, 0);
if (name_len <= 0 || name == NULL)
return -1;
MemoryDeleter nameDeleter(name);
directory_cookie::entry* entry =
(directory_cookie::entry*)malloc(sizeof(directory_cookie::entry) + name_len);
if (entry == NULL)
return -1;
entry->next = NULL;
entry->inode = MREF(mref);
entry->name_length = name_len;
memcpy(entry->name, name, name_len + 1);
if (cookie->first == NULL) {
cookie->first = cookie->current = entry;
} else {
cookie->current->next = entry;
cookie->current = entry;
}
return 0;
}
static status_t
fs_read_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie,
struct dirent* dirent, size_t bufferSize, uint32* _num)
{
CALLED();
directory_cookie* cookie = (directory_cookie*)_cookie;
if (cookie->first == NULL) {
volume* volume = (struct volume*)_volume->private_volume;
MutexLocker lock(volume->lock);
vnode* node = (vnode*)_node->private_node;
ntfs_inode* ni = ntfs_inode_open(volume->ntfs, node->inode);
if (ni == NULL)
return errno;
NtfsInodeCloser niCloser(ni);
s64 pos = 0;
if (ntfs_readdir(ni, &pos, cookie, _ntfs_readdir_callback) != 0)
return errno;
cookie->current = cookie->first;
}
if (cookie->first == NULL)
return ENOENT;
if (cookie->current == NULL) {
*_num = 0;
return B_OK;
}
uint32 maxCount = *_num;
uint32 count = 0;
while (count < maxCount && bufferSize > sizeof(struct dirent)) {
size_t length = bufferSize - offsetof(struct dirent, d_name);
if (length < (cookie->current->name_length + 1)) {
if (count == 0)
return B_BUFFER_OVERFLOW;
break;
}
length = cookie->current->name_length;
dirent->d_dev = _volume->id;
dirent->d_ino = cookie->current->inode;
strlcpy(dirent->d_name, cookie->current->name, length + 1);
dirent = next_dirent(dirent, length, bufferSize);
count++;
cookie->current = cookie->current->next;
if (cookie->current == NULL)
break;
}
*_num = count;
return B_OK;
}
static status_t
fs_rewind_dir(fs_volume* , fs_vnode* , void *_cookie)
{
CALLED();
directory_cookie* cookie = (directory_cookie*)_cookie;
cookie->current = cookie->first;
return B_OK;
}
static status_t
fs_close_dir(fs_volume* , fs_vnode* , void* )
{
return B_OK;
}
static status_t
fs_free_dir_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie)
{
CALLED();
directory_cookie* cookie = (directory_cookie*)_cookie;
if (cookie == NULL)
return B_OK;
cookie->current = cookie->first;
while (cookie->current != NULL) {
directory_cookie::entry* next = cookie->current->next;
free(cookie->current);
cookie->current = next;
}
delete cookie;
return B_OK;
}
fs_volume_ops gNtfsVolumeOps = {
&fs_unmount,
&fs_read_fs_info,
&fs_write_fs_info,
NULL,
&fs_get_vnode,
};
fs_vnode_ops gNtfsVnodeOps = {
&fs_lookup,
&fs_get_vnode_name,
&fs_put_vnode,
&fs_remove_vnode,
&fs_can_page,
&fs_read_pages,
&fs_write_pages,
NULL,
NULL,
NULL,
&fs_ioctl,
NULL,
NULL,
NULL,
&fs_fsync,
&fs_read_link,
NULL,
NULL,
&fs_unlink,
&fs_rename,
&fs_access,
&fs_read_stat,
&fs_write_stat,
NULL,
&fs_create,
&fs_open,
&fs_close,
&fs_free_cookie,
&fs_read,
&fs_write,
&fs_create_dir,
&fs_remove_dir,
&fs_open_dir,
&fs_close_dir,
&fs_free_dir_cookie,
&fs_read_dir,
&fs_rewind_dir,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
};
static file_system_module_info sNtfsFileSystem = {
{
"file_systems/ntfs" B_CURRENT_FS_API_VERSION,
0,
NULL,
},
"ntfs",
"NT File System",
0
| B_DISK_SYSTEM_IS_FILE_SYSTEM
| B_DISK_SYSTEM_SUPPORTS_INITIALIZING
| B_DISK_SYSTEM_SUPPORTS_CONTENT_NAME
| B_DISK_SYSTEM_SUPPORTS_WRITING
,
fs_identify_partition,
fs_scan_partition,
fs_free_identify_partition_cookie,
NULL,
&fs_mount,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
fs_initialize,
NULL
};
module_info *modules[] = {
(module_info *)&sNtfsFileSystem,
NULL,
};