mirror of
https://github.com/Fledge68/WiiFlow_Lite.git
synced 2024-12-26 20:01:55 +01:00
663 lines
20 KiB
C
663 lines
20 KiB
C
/**
|
|
* ntfs.c - Simple functionality for startup, mounting and unmounting of NTFS-based devices.
|
|
*
|
|
* Copyright (c) 2009 Rhys "Shareese" Koedijk
|
|
* Copyright (c) 2006 Michael "Chishm" Chisholm
|
|
*
|
|
* This program/include file is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as published
|
|
* by the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program/include file is distributed in the hope that it will be
|
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif
|
|
#ifdef HAVE_ERRNO_H
|
|
#include <errno.h>
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
|
|
#include "ntfs.h"
|
|
#include "ntfsinternal.h"
|
|
#include "ntfsfile.h"
|
|
#include "ntfsdir.h"
|
|
#include "gekko_io.h"
|
|
#include "cache.h"
|
|
|
|
// NTFS device driver devoptab
|
|
static const devoptab_t devops_ntfs = {
|
|
NULL, /* Device name */
|
|
sizeof (ntfs_file_state),
|
|
ntfs_open_r,
|
|
ntfs_close_r,
|
|
ntfs_write_r,
|
|
ntfs_read_r,
|
|
ntfs_seek_r,
|
|
ntfs_fstat_r,
|
|
ntfs_stat_r,
|
|
ntfs_link_r,
|
|
ntfs_unlink_r,
|
|
ntfs_chdir_r,
|
|
ntfs_rename_r,
|
|
ntfs_mkdir_r,
|
|
sizeof (ntfs_dir_state),
|
|
ntfs_diropen_r,
|
|
ntfs_dirreset_r,
|
|
ntfs_dirnext_r,
|
|
ntfs_dirclose_r,
|
|
ntfs_statvfs_r,
|
|
ntfs_ftruncate_r,
|
|
ntfs_fsync_r,
|
|
NULL /* Device data */
|
|
};
|
|
|
|
void ntfsInit (void)
|
|
{
|
|
static bool isInit = false;
|
|
|
|
// Initialise ntfs-3g (if not already done so)
|
|
if (!isInit) {
|
|
isInit = true;
|
|
|
|
// Set the log handler
|
|
#ifdef NTFS_ENABLE_LOG
|
|
ntfs_log_set_handler(ntfs_log_handler_stderr);
|
|
#else
|
|
ntfs_log_set_handler(ntfs_log_handler_null);
|
|
#endif
|
|
// Set our current local
|
|
ntfs_set_locale();
|
|
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
int ntfsFindPartitions (const DISC_INTERFACE *interface, sec_t **partitions)
|
|
{
|
|
MASTER_BOOT_RECORD mbr;
|
|
PARTITION_RECORD *partition = NULL;
|
|
sec_t partition_starts[NTFS_MAX_PARTITIONS] = {0};
|
|
int partition_count = 0;
|
|
sec_t part_lba = 0;
|
|
int i;
|
|
|
|
union {
|
|
u8 buffer[MAX_SECTOR_SIZE];
|
|
MASTER_BOOT_RECORD mbr;
|
|
EXTENDED_BOOT_RECORD ebr;
|
|
NTFS_BOOT_SECTOR boot;
|
|
} sector;
|
|
|
|
// Sanity check
|
|
if (!interface) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
if (!partitions)
|
|
return 0;
|
|
|
|
// Initialise ntfs-3g
|
|
ntfsInit();
|
|
|
|
// Start the device and check that it is inserted
|
|
if (!interface->startup()) {
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
if (!interface->isInserted()) {
|
|
return 0;
|
|
}
|
|
|
|
// Read the first sector on the device
|
|
if (!interface->readSectors(0, 1, §or.buffer)) {
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
// If this is the devices master boot record
|
|
if (sector.mbr.signature == MBR_SIGNATURE) {
|
|
memcpy(&mbr, §or, sizeof(MASTER_BOOT_RECORD));
|
|
ntfs_log_debug("Valid Master Boot Record found\n");
|
|
|
|
// Search the partition table for all NTFS partitions (max. 4 primary partitions)
|
|
for (i = 0; i < 4; i++) {
|
|
partition = &mbr.partitions[i];
|
|
part_lba = le32_to_cpu(mbr.partitions[i].lba_start);
|
|
|
|
ntfs_log_debug("Partition %i: %s, sector %d, type 0x%x\n", i + 1,
|
|
partition->status == PARTITION_STATUS_BOOTABLE ? "bootable (active)" : "non-bootable",
|
|
part_lba, partition->type);
|
|
|
|
// Figure out what type of partition this is
|
|
switch (partition->type) {
|
|
|
|
// Ignore empty partitions
|
|
case PARTITION_TYPE_EMPTY:
|
|
continue;
|
|
|
|
// NTFS partition
|
|
case PARTITION_TYPE_NTFS: {
|
|
ntfs_log_debug("Partition %i: Claims to be NTFS\n", i + 1);
|
|
|
|
// Read and validate the NTFS partition
|
|
if (interface->readSectors(part_lba, 1, §or)) {
|
|
if (sector.boot.oem_id == NTFS_OEM_ID) {
|
|
ntfs_log_debug("Partition %i: Valid NTFS boot sector found\n", i + 1);
|
|
if (partition_count < NTFS_MAX_PARTITIONS) {
|
|
partition_starts[partition_count] = part_lba;
|
|
partition_count++;
|
|
}
|
|
} else {
|
|
ntfs_log_debug("Partition %i: Invalid NTFS boot sector, not actually NTFS\n", i + 1);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// DOS 3.3+ or Windows 95 extended partition
|
|
case PARTITION_TYPE_DOS33_EXTENDED:
|
|
case PARTITION_TYPE_WIN95_EXTENDED: {
|
|
ntfs_log_debug("Partition %i: Claims to be Extended\n", i + 1);
|
|
|
|
// Walk the extended partition chain, finding all NTFS partitions within it
|
|
sec_t ebr_lba = part_lba;
|
|
sec_t next_erb_lba = 0;
|
|
do {
|
|
|
|
// Read and validate the extended boot record
|
|
if (interface->readSectors(ebr_lba + next_erb_lba, 1, §or)) {
|
|
if (sector.ebr.signature == EBR_SIGNATURE) {
|
|
ntfs_log_debug("Logical Partition @ %d: %s type 0x%x\n", ebr_lba + next_erb_lba,
|
|
sector.ebr.partition.status == PARTITION_STATUS_BOOTABLE ? "bootable (active)" : "non-bootable",
|
|
sector.ebr.partition.type);
|
|
|
|
// Get the start sector of the current partition
|
|
// and the next extended boot record in the chain
|
|
part_lba = ebr_lba + next_erb_lba + le32_to_cpu(sector.ebr.partition.lba_start);
|
|
next_erb_lba = le32_to_cpu(sector.ebr.next_ebr.lba_start);
|
|
|
|
// Check if this partition has a valid NTFS boot record
|
|
if (interface->readSectors(part_lba, 1, §or)) {
|
|
if (sector.boot.oem_id == NTFS_OEM_ID) {
|
|
ntfs_log_debug("Logical Partition @ %d: Valid NTFS boot sector found\n", part_lba);
|
|
if(sector.ebr.partition.type != PARTITION_TYPE_NTFS) {
|
|
ntfs_log_warning("Logical Partition @ %d: Is NTFS but type is 0x%x; 0x%x was expected\n", part_lba, sector.ebr.partition.type, PARTITION_TYPE_NTFS);
|
|
}
|
|
if (partition_count < NTFS_MAX_PARTITIONS) {
|
|
partition_starts[partition_count] = part_lba;
|
|
partition_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
next_erb_lba = 0;
|
|
}
|
|
}
|
|
|
|
} while (next_erb_lba);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// Unknown or unsupported partition type
|
|
default: {
|
|
|
|
// Check if this partition has a valid NTFS boot record anyway,
|
|
// it might be misrepresented due to a lazy partition editor
|
|
if (interface->readSectors(part_lba, 1, §or)) {
|
|
if (sector.boot.oem_id == NTFS_OEM_ID) {
|
|
ntfs_log_debug("Partition %i: Valid NTFS boot sector found\n", i + 1);
|
|
if(partition->type != PARTITION_TYPE_NTFS) {
|
|
ntfs_log_warning("Partition %i: Is NTFS but type is 0x%x; 0x%x was expected\n", i + 1, partition->type, PARTITION_TYPE_NTFS);
|
|
}
|
|
if (partition_count < NTFS_MAX_PARTITIONS) {
|
|
partition_starts[partition_count] = part_lba;
|
|
partition_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Else it is assumed this device has no master boot record
|
|
} else {
|
|
ntfs_log_debug("No Master Boot Record was found!\n");
|
|
|
|
// As a last-ditched effort, search the first 64 sectors of the device for stray NTFS partitions
|
|
for (i = 0; i < 64; i++) {
|
|
if (interface->readSectors(i, 1, §or)) {
|
|
if (sector.boot.oem_id == NTFS_OEM_ID) {
|
|
ntfs_log_debug("Valid NTFS boot sector found at sector %d!\n", i);
|
|
if (partition_count < NTFS_MAX_PARTITIONS) {
|
|
partition_starts[partition_count] = i;
|
|
partition_count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Return the found partitions (if any)
|
|
if (partition_count > 0) {
|
|
*partitions = (sec_t*)ntfs_alloc(sizeof(sec_t) * partition_count);
|
|
if (*partitions) {
|
|
memcpy(*partitions, &partition_starts, sizeof(sec_t) * partition_count);
|
|
return partition_count;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ntfsMountAll (ntfs_md **mounts, u32 flags)
|
|
{
|
|
const INTERFACE_ID *discs = ntfsGetDiscInterfaces();
|
|
const INTERFACE_ID *disc = NULL;
|
|
ntfs_md mount_points[NTFS_MAX_MOUNTS];
|
|
sec_t *partitions = NULL;
|
|
int mount_count = 0;
|
|
int partition_count = 0;
|
|
char name[128];
|
|
int i, j, k;
|
|
|
|
// Initialise ntfs-3g
|
|
ntfsInit();
|
|
|
|
// Find and mount all NTFS partitions on all known devices
|
|
for (i = 0; discs[i].name != NULL && discs[i].interface != NULL; i++) {
|
|
disc = &discs[i];
|
|
partition_count = ntfsFindPartitions(disc->interface, &partitions);
|
|
if (partition_count > 0 && partitions) {
|
|
for (j = 0, k = 0; j < partition_count; j++) {
|
|
|
|
// Find the next unused mount name
|
|
do {
|
|
sprintf(name, "%s%i", NTFS_MOUNT_PREFIX, k++);
|
|
if (k >= NTFS_MAX_MOUNTS) {
|
|
ntfs_free(partitions);
|
|
errno = EADDRNOTAVAIL;
|
|
return -1;
|
|
}
|
|
} while (ntfsGetDevice(name, false));
|
|
|
|
// Mount the partition
|
|
if (mount_count < NTFS_MAX_MOUNTS) {
|
|
if (ntfsMount(name, disc->interface, partitions[j], CACHE_DEFAULT_PAGE_SIZE, CACHE_DEFAULT_PAGE_COUNT, flags)) {
|
|
strcpy(mount_points[mount_count].name, name);
|
|
mount_points[mount_count].interface = disc->interface;
|
|
mount_points[mount_count].startSector = partitions[j];
|
|
mount_count++;
|
|
}
|
|
}
|
|
|
|
}
|
|
ntfs_free(partitions);
|
|
}
|
|
}
|
|
|
|
// Return the mounts (if any)
|
|
if (mount_count > 0 && mounts) {
|
|
*mounts = (ntfs_md*)ntfs_alloc(sizeof(ntfs_md) * mount_count);
|
|
if (*mounts) {
|
|
memcpy(*mounts, &mount_points, sizeof(ntfs_md) * mount_count);
|
|
return mount_count;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ntfsMountDevice (const DISC_INTERFACE *interface, ntfs_md **mounts, u32 flags)
|
|
{
|
|
const INTERFACE_ID *discs = ntfsGetDiscInterfaces();
|
|
const INTERFACE_ID *disc = NULL;
|
|
ntfs_md mount_points[NTFS_MAX_MOUNTS];
|
|
sec_t *partitions = NULL;
|
|
int mount_count = 0;
|
|
int partition_count = 0;
|
|
char name[128];
|
|
int i, j, k;
|
|
|
|
// Sanity check
|
|
if (!interface) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
// Initialise ntfs-3g
|
|
ntfsInit();
|
|
|
|
// Find the specified device then find and mount all NTFS partitions on it
|
|
for (i = 0; discs[i].name != NULL && discs[i].interface != NULL; i++) {
|
|
if (discs[i].interface == interface) {
|
|
disc = &discs[i];
|
|
partition_count = ntfsFindPartitions(disc->interface, &partitions);
|
|
if (partition_count > 0 && partitions) {
|
|
for (j = 0, k = 0; j < partition_count; j++) {
|
|
|
|
// Find the next unused mount name
|
|
do {
|
|
sprintf(name, "%s%i", NTFS_MOUNT_PREFIX, k++);
|
|
if (k >= NTFS_MAX_MOUNTS) {
|
|
ntfs_free(partitions);
|
|
errno = EADDRNOTAVAIL;
|
|
return -1;
|
|
}
|
|
} while (ntfsGetDevice(name, false));
|
|
|
|
// Mount the partition
|
|
if (mount_count < NTFS_MAX_MOUNTS) {
|
|
if (ntfsMount(name, disc->interface, partitions[j], CACHE_DEFAULT_PAGE_SIZE, CACHE_DEFAULT_PAGE_COUNT, flags)) {
|
|
strcpy(mount_points[mount_count].name, name);
|
|
mount_points[mount_count].interface = disc->interface;
|
|
mount_points[mount_count].startSector = partitions[j];
|
|
mount_count++;
|
|
}
|
|
}
|
|
|
|
}
|
|
ntfs_free(partitions);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we couldn't find the device then return with error status
|
|
if (!disc) {
|
|
errno = ENODEV;
|
|
return -1;
|
|
}
|
|
|
|
// Return the mounts (if any)
|
|
if (mount_count > 0 && mounts) {
|
|
*mounts = (ntfs_md*)ntfs_alloc(sizeof(ntfs_md) * mount_count);
|
|
if (*mounts) {
|
|
memcpy(*mounts, &mount_points, sizeof(ntfs_md) * mount_count);
|
|
return mount_count;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool ntfsMount (const char *name, const DISC_INTERFACE *interface, sec_t startSector, u32 cachePageCount, u32 cachePageSize, u32 flags)
|
|
{
|
|
ntfs_vd *vd = NULL;
|
|
gekko_fd *fd = NULL;
|
|
|
|
// Sanity check
|
|
if (!name || !interface) {
|
|
errno = EINVAL;
|
|
return false;
|
|
}
|
|
|
|
// Initialise ntfs-3g
|
|
ntfsInit();
|
|
|
|
// Check that the requested mount name is free
|
|
if (ntfsGetDevice(name, false)) {
|
|
errno = EADDRINUSE;
|
|
return false;
|
|
}
|
|
|
|
// Check that we can at least read from this device
|
|
if (!(interface->features & FEATURE_MEDIUM_CANREAD)) {
|
|
errno = EPERM;
|
|
return false;
|
|
}
|
|
|
|
// Allocate the volume descriptor
|
|
vd = (ntfs_vd*)ntfs_alloc(sizeof(ntfs_vd));
|
|
if (!vd) {
|
|
errno = ENOMEM;
|
|
return false;
|
|
}
|
|
|
|
// Setup the volume descriptor
|
|
vd->id = interface->ioType;
|
|
vd->flags = 0;
|
|
vd->uid = 0;
|
|
vd->gid = 0;
|
|
vd->fmask = 0;
|
|
vd->dmask = 0;
|
|
vd->atime = ((flags & NTFS_UPDATE_ACCESS_TIMES) ? ATIME_ENABLED : ATIME_DISABLED);
|
|
vd->showHiddenFiles = (flags & NTFS_SHOW_HIDDEN_FILES);
|
|
vd->showSystemFiles = (flags & NTFS_SHOW_SYSTEM_FILES);
|
|
|
|
// Allocate the device driver descriptor
|
|
fd = (gekko_fd*)ntfs_alloc(sizeof(gekko_fd));
|
|
if (!fd) {
|
|
ntfs_free(vd);
|
|
errno = ENOMEM;
|
|
return false;
|
|
}
|
|
|
|
// Setup the device driver descriptor
|
|
fd->interface = interface;
|
|
fd->startSector = startSector;
|
|
fd->sectorSize = 0;
|
|
fd->sectorCount = 0;
|
|
fd->cachePageCount = cachePageCount;
|
|
fd->cachePageSize = cachePageSize;
|
|
|
|
// Allocate the device driver
|
|
vd->dev = ntfs_device_alloc(name, 0, &ntfs_device_gekko_io_ops, fd);
|
|
if (!vd->dev) {
|
|
ntfs_free(fd);
|
|
ntfs_free(vd);
|
|
return false;
|
|
}
|
|
|
|
// Build the mount flags
|
|
if (flags & NTFS_READ_ONLY)
|
|
vd->flags |= MS_RDONLY;
|
|
else
|
|
{
|
|
if (!(interface->features & FEATURE_MEDIUM_CANWRITE))
|
|
vd->flags |= MS_RDONLY;
|
|
if ((interface->features & FEATURE_MEDIUM_CANREAD) && (interface->features & FEATURE_MEDIUM_CANWRITE))
|
|
vd->flags |= MS_EXCLUSIVE;
|
|
}
|
|
if (flags & NTFS_RECOVER)
|
|
vd->flags |= MS_RECOVER;
|
|
if (flags & NTFS_IGNORE_HIBERFILE)
|
|
vd->flags |= MS_IGNORE_HIBERFILE;
|
|
|
|
if (vd->flags & MS_RDONLY)
|
|
ntfs_log_debug("Mounting \"%s\" as read-only\n", name);
|
|
|
|
// Mount the device
|
|
vd->vol = ntfs_device_mount(vd->dev, vd->flags);
|
|
if (!vd->vol) {
|
|
switch(ntfs_volume_error(errno)) {
|
|
case NTFS_VOLUME_NOT_NTFS: errno = EINVALPART; break;
|
|
case NTFS_VOLUME_CORRUPT: errno = EINVALPART; break;
|
|
case NTFS_VOLUME_HIBERNATED: errno = EHIBERNATED; break;
|
|
case NTFS_VOLUME_UNCLEAN_UNMOUNT: errno = EDIRTY; break;
|
|
default: errno = EINVAL; break;
|
|
}
|
|
ntfs_device_free(vd->dev);
|
|
ntfs_free(vd);
|
|
return false;
|
|
}
|
|
|
|
if (flags & NTFS_IGNORE_CASE)
|
|
ntfs_set_ignore_case(vd->vol);
|
|
|
|
// Initialise the volume descriptor
|
|
if (ntfsInitVolume(vd)) {
|
|
ntfs_umount(vd->vol, true);
|
|
ntfs_free(vd);
|
|
return false;
|
|
}
|
|
|
|
// Add the device to the devoptab table
|
|
if (ntfsAddDevice(name, vd)) {
|
|
ntfsDeinitVolume(vd);
|
|
ntfs_umount(vd->vol, true);
|
|
ntfs_free(vd);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ntfsUnmount (const char *name, bool force)
|
|
{
|
|
ntfs_vd *vd = NULL;
|
|
|
|
// Get the devices volume descriptor
|
|
vd = ntfsGetVolume(name);
|
|
if (!vd)
|
|
return;
|
|
|
|
// Remove the device from the devoptab table
|
|
ntfsRemoveDevice(name);
|
|
|
|
// Deinitialise the volume descriptor
|
|
ntfsDeinitVolume(vd);
|
|
|
|
// Unmount the volume
|
|
ntfs_umount(vd->vol, force);
|
|
|
|
// Free the volume descriptor
|
|
ntfs_free(vd);
|
|
|
|
return;
|
|
}
|
|
|
|
const char *ntfsGetVolumeName (const char *name)
|
|
{
|
|
ntfs_vd *vd = NULL;
|
|
|
|
// Sanity check
|
|
if (!name) {
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
// Get the devices volume descriptor
|
|
vd = ntfsGetVolume(name);
|
|
if (!vd) {
|
|
errno = ENODEV;
|
|
return NULL;
|
|
}
|
|
return vd->vol->vol_name;
|
|
}
|
|
|
|
bool ntfsSetVolumeName (const char *name, const char *volumeName)
|
|
{
|
|
ntfs_vd *vd = NULL;
|
|
ntfs_attr *na = NULL;
|
|
ntfschar *ulabel = NULL;
|
|
int ulabel_len;
|
|
|
|
// Sanity check
|
|
if (!name) {
|
|
errno = EINVAL;
|
|
return false;
|
|
}
|
|
|
|
// Get the devices volume descriptor
|
|
vd = ntfsGetVolume(name);
|
|
if (!vd) {
|
|
errno = ENODEV;
|
|
return false;
|
|
}
|
|
|
|
// Lock
|
|
ntfsLock(vd);
|
|
|
|
// Convert the new volume name to unicode
|
|
ulabel_len = ntfsLocalToUnicode(volumeName, &ulabel) * sizeof(ntfschar);
|
|
if (ulabel_len < 0) {
|
|
ntfsUnlock(vd);
|
|
errno = EINVAL;
|
|
return false;
|
|
}
|
|
|
|
// Check if the volume name attribute exists
|
|
na = ntfs_attr_open(vd->vol->vol_ni, AT_VOLUME_NAME, NULL, 0);
|
|
if (na) {
|
|
|
|
// It does, resize it to match the length of the new volume name
|
|
if (ntfs_attr_truncate(na, ulabel_len)) {
|
|
free(ulabel);
|
|
ntfsUnlock(vd);
|
|
return false;
|
|
}
|
|
|
|
// Write the new volume name
|
|
if (ntfs_attr_pwrite(na, 0, ulabel_len, ulabel) != ulabel_len) {
|
|
free(ulabel);
|
|
ntfsUnlock(vd);
|
|
return false;
|
|
}
|
|
|
|
} else {
|
|
|
|
// It doesn't, create it now
|
|
if (ntfs_attr_add(vd->vol->vol_ni, AT_VOLUME_NAME, NULL, 0, (u8*)ulabel, ulabel_len)) {
|
|
free(ulabel);
|
|
ntfsUnlock(vd);
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
// Reset the volumes name cache (as it has now been changed)
|
|
vd->name[0] = '\0';
|
|
|
|
// Close the volume name attribute
|
|
if (na)
|
|
ntfs_attr_close(na);
|
|
|
|
// Sync the volume node
|
|
if (ntfs_inode_sync(vd->vol->vol_ni)) {
|
|
free(ulabel);
|
|
ntfsUnlock(vd);
|
|
return false;
|
|
}
|
|
|
|
// Clean up
|
|
free(ulabel);
|
|
|
|
// Unlock
|
|
ntfsUnlock(vd);
|
|
|
|
return true;
|
|
}
|
|
|
|
const devoptab_t *ntfsGetDevOpTab (void)
|
|
{
|
|
return &devops_ntfs;
|
|
}
|