2009-12-19 15:06:57 +01:00
|
|
|
/**
|
|
|
|
* ntfsinternal.h - Internal support routines for NTFS-based devices.
|
|
|
|
*
|
2010-02-01 00:14:14 +01:00
|
|
|
* Copyright (c) 2010 Dimok
|
2009-12-19 15:06:57 +01:00
|
|
|
* 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 "ntfsinternal.h"
|
|
|
|
#include "ntfsdir.h"
|
|
|
|
#include "ntfsfile.h"
|
|
|
|
|
|
|
|
#if defined(__wii__)
|
|
|
|
#include <sdcard/wiisd_io.h>
|
|
|
|
#include <sdcard/gcsd.h>
|
2010-02-22 22:29:47 +01:00
|
|
|
|
|
|
|
#include "usbloader/usbstorage2.h"
|
2009-12-19 15:06:57 +01:00
|
|
|
|
|
|
|
const INTERFACE_ID ntfs_disc_interfaces[] = {
|
|
|
|
{ "sd", &__io_wiisd },
|
2010-02-22 22:29:47 +01:00
|
|
|
{ "usb", &__io_usbstorage2 },
|
2009-12-19 15:06:57 +01:00
|
|
|
{ "carda", &__io_gcsda },
|
|
|
|
{ "cardb", &__io_gcsdb },
|
|
|
|
{ NULL, NULL }
|
|
|
|
};
|
|
|
|
|
|
|
|
#elif defined(__gamecube__)
|
|
|
|
#include <sdcard/gcsd.h>
|
|
|
|
|
|
|
|
const INTERFACE_ID ntfs_disc_interfaces[] = {
|
|
|
|
{ "carda", &__io_gcsda },
|
|
|
|
{ "cardb", &__io_gcsdb },
|
|
|
|
{ NULL, NULL }
|
|
|
|
};
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
int ntfsAddDevice (const char *name, void *deviceData)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
const devoptab_t *devoptab_ntfs = ntfsGetDevOpTab();
|
|
|
|
devoptab_t *dev = NULL;
|
|
|
|
char *devname = NULL;
|
|
|
|
int i;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Sanity check
|
|
|
|
if (!name || !deviceData || !devoptab_ntfs) {
|
|
|
|
errno = EINVAL;
|
|
|
|
return -1;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Allocate a devoptab for this device
|
|
|
|
dev = ntfs_alloc(sizeof(devoptab_t) + strlen(name) + 1);
|
|
|
|
if (!dev) {
|
|
|
|
errno = ENOMEM;
|
|
|
|
return false;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Use the space allocated at the end of the devoptab for storing the device name
|
|
|
|
devname = (char*)(dev + 1);
|
|
|
|
strcpy(devname, name);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Setup the devoptab
|
|
|
|
memcpy(dev, devoptab_ntfs, sizeof(devoptab_t));
|
|
|
|
dev->name = devname;
|
|
|
|
dev->deviceData = deviceData;
|
|
|
|
|
|
|
|
// Add the device to the devoptab table (if there is a free slot)
|
|
|
|
for (i = 0; i < STD_MAX; i++) {
|
|
|
|
if (devoptab_list[i] == devoptab_list[0] && i != 0) {
|
|
|
|
devoptab_list[i] = dev;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// If we reach here then there are no free slots in the devoptab table for this device
|
|
|
|
errno = EADDRNOTAVAIL;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
void ntfsRemoveDevice (const char *path)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
const devoptab_t *devoptab = NULL;
|
|
|
|
char name[128] = {0};
|
|
|
|
int i;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Get the device name from the path
|
|
|
|
strncpy(name, path, 127);
|
|
|
|
strtok(name, ":/");
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Find and remove the specified device from the devoptab table
|
|
|
|
// NOTE: We do this manually due to a 'bug' in RemoveDevice
|
|
|
|
// which ignores names with suffixes and causes names
|
|
|
|
// like "ntfs" and "ntfs1" to be seen as equals
|
|
|
|
for (i = 0; i < STD_MAX; i++) {
|
|
|
|
devoptab = devoptab_list[i];
|
|
|
|
if (devoptab && devoptab->name) {
|
|
|
|
if (strcmp(name, devoptab->name) == 0) {
|
|
|
|
devoptab_list[i] = devoptab_list[0];
|
|
|
|
ntfs_free((devoptab_t*)devoptab);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
const devoptab_t *ntfsGetDevice (const char *path, bool useDefaultDevice)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
const devoptab_t *devoptab = NULL;
|
|
|
|
char name[128] = {0};
|
|
|
|
int i;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Get the device name from the path
|
|
|
|
strncpy(name, path, 127);
|
|
|
|
strtok(name, ":/");
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Search the devoptab table for the specified device name
|
2010-02-01 00:14:14 +01:00
|
|
|
// NOTE: We do this manually due to a 'bug' in GetDeviceOpTab
|
2009-12-19 15:06:57 +01:00
|
|
|
// which ignores names with suffixes and causes names
|
|
|
|
// like "ntfs" and "ntfs1" to be seen as equals
|
|
|
|
for (i = 0; i < STD_MAX; i++) {
|
|
|
|
devoptab = devoptab_list[i];
|
|
|
|
if (devoptab && devoptab->name) {
|
|
|
|
if (strcmp(name, devoptab->name) == 0) {
|
|
|
|
return devoptab;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// If we reach here then we couldn't find the device name,
|
2010-02-01 00:14:14 +01:00
|
|
|
// chances are that this path has no device name in it.
|
2009-12-19 15:06:57 +01:00
|
|
|
// Call GetDeviceOpTab to get our default device (chdir).
|
|
|
|
if (useDefaultDevice)
|
|
|
|
return GetDeviceOpTab("");
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
const INTERFACE_ID *ntfsGetDiscInterfaces (void)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
// Get all know disc interfaces on the host system
|
|
|
|
return ntfs_disc_interfaces;
|
|
|
|
}
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
ntfs_vd *ntfsGetVolume (const char *path)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
// Get the volume descriptor from the paths associated devoptab (if found)
|
|
|
|
const devoptab_t *devoptab_ntfs = ntfsGetDevOpTab();
|
|
|
|
const devoptab_t *devoptab = ntfsGetDevice(path, true);
|
|
|
|
if (devoptab && devoptab_ntfs && (devoptab->open_r == devoptab_ntfs->open_r))
|
|
|
|
return (ntfs_vd*)devoptab->deviceData;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
int ntfsInitVolume (ntfs_vd *vd)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
// Sanity check
|
|
|
|
if (!vd) {
|
|
|
|
errno = ENODEV;
|
|
|
|
return -1;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Initialise the volume lock
|
|
|
|
LWP_MutexInit(&vd->lock, false);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
|
|
|
// Reset the volumes name cache
|
|
|
|
vd->name[0] = '\0';
|
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Reset the volumes current directory
|
|
|
|
vd->cwd_ni = NULL;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Reset open directory and file stats
|
|
|
|
vd->openDirCount = 0;
|
|
|
|
vd->openFileCount = 0;
|
|
|
|
vd->firstOpenDir = NULL;
|
|
|
|
vd->firstOpenFile = NULL;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
void ntfsDeinitVolume (ntfs_vd *vd)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
// Sanity check
|
|
|
|
if (!vd) {
|
|
|
|
errno = ENODEV;
|
|
|
|
return;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Lock
|
|
|
|
ntfsLock(vd);
|
|
|
|
|
|
|
|
// Close any directories which are still open (lazy programmers!)
|
|
|
|
ntfs_dir_state *nextDir = vd->firstOpenDir;
|
|
|
|
while (nextDir) {
|
|
|
|
ntfs_log_warning("Cleaning up orphaned directory @ %p\n", nextDir);
|
|
|
|
ntfsCloseDir(nextDir);
|
|
|
|
nextDir = nextDir->nextOpenDir;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Close any files which are still open (lazy programmers!)
|
|
|
|
ntfs_file_state *nextFile = vd->firstOpenFile;
|
|
|
|
while (nextFile) {
|
|
|
|
ntfs_log_warning("Cleaning up orphaned file @ %p\n", nextFile);
|
|
|
|
ntfsCloseFile(nextFile);
|
|
|
|
nextFile = nextFile->nextOpenFile;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Reset open directory and file stats
|
|
|
|
vd->openDirCount = 0;
|
|
|
|
vd->openFileCount = 0;
|
|
|
|
vd->firstOpenDir = NULL;
|
|
|
|
vd->firstOpenFile = NULL;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Close the volumes current directory (if any)
|
2010-02-01 00:14:14 +01:00
|
|
|
//if (vd->cwd_ni) {
|
2010-02-09 11:59:55 +01:00
|
|
|
//ntfsCloseEntry(vd, vd->cwd_ni);
|
|
|
|
//vd->cwd_ni = NULL;
|
2010-02-01 00:14:14 +01:00
|
|
|
//}
|
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Force the underlying device to sync
|
|
|
|
vd->dev->d_ops->sync(vd->dev);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Unlock
|
|
|
|
ntfsUnlock(vd);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Deinitialise the volume lock
|
|
|
|
LWP_MutexDestroy(vd->lock);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
ntfs_inode *ntfsOpenEntry (ntfs_vd *vd, const char *path)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
return ntfsParseEntry(vd, path, 1);
|
|
|
|
}
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
ntfs_inode *ntfsParseEntry (ntfs_vd *vd, const char *path, int reparseLevel)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
ntfs_inode *ni = NULL;
|
|
|
|
char *target = NULL;
|
|
|
|
int attr_size;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Sanity check
|
|
|
|
if (!vd) {
|
|
|
|
errno = ENODEV;
|
|
|
|
return NULL;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Get the actual path of the entry
|
|
|
|
path = ntfsRealPath(path);
|
|
|
|
if (!path) {
|
|
|
|
errno = EINVAL;
|
|
|
|
return NULL;
|
|
|
|
} else if (path[0] == '\0') {
|
|
|
|
path = ".";
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the entry, taking into account our current directory (if any)
|
|
|
|
if (path[0] != PATH_SEP)
|
|
|
|
ni = ntfs_pathname_to_inode(vd->vol, vd->cwd_ni, path++);
|
|
|
|
else
|
|
|
|
ni = ntfs_pathname_to_inode(vd->vol, NULL, path);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// If the entry was found and it has reparse data then parse its true path;
|
|
|
|
// this resolves the true location of symbolic links and directory junctions
|
|
|
|
if (ni && (ni->flags & FILE_ATTR_REPARSE_POINT)) {
|
|
|
|
if (ntfs_possible_symlink(ni)) {
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Sanity check, give up if we are parsing to deep
|
|
|
|
if (reparseLevel > NTFS_MAX_SYMLINK_DEPTH) {
|
|
|
|
ntfsCloseEntry(vd, ni);
|
|
|
|
errno = ELOOP;
|
|
|
|
return NULL;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Get the target path of this entry
|
|
|
|
target = ntfs_make_symlink(path, ni, &attr_size);
|
|
|
|
if (!target) {
|
|
|
|
ntfsCloseEntry(vd, ni);
|
|
|
|
return NULL;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Close the entry (we are no longer interested in it)
|
|
|
|
ntfsCloseEntry(vd, ni);
|
|
|
|
|
|
|
|
// Parse the entries target
|
|
|
|
ni = ntfsParseEntry(vd, target, reparseLevel++);
|
|
|
|
|
|
|
|
// Clean up
|
|
|
|
ntfs_free(target);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
}
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
return ni;
|
|
|
|
}
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
void ntfsCloseEntry (ntfs_vd *vd, ntfs_inode *ni)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
// Sanity check
|
|
|
|
if (!vd) {
|
|
|
|
errno = ENODEV;
|
|
|
|
return;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Lock
|
|
|
|
ntfsLock(vd);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Sync the entry (if it is dirty)
|
|
|
|
if (NInoDirty(ni))
|
|
|
|
ntfsSync(vd, ni);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Close the entry
|
|
|
|
ntfs_inode_close(ni);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Unlock
|
|
|
|
ntfsUnlock(vd);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
ntfs_inode *ntfsCreate (ntfs_vd *vd, const char *path, mode_t type, const char *target)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
ntfs_inode *dir_ni = NULL, *ni = NULL;
|
|
|
|
char *dir = NULL;
|
|
|
|
char *name = NULL;
|
|
|
|
ntfschar *uname = NULL, *utarget = NULL;
|
|
|
|
int uname_len, utarget_len;
|
|
|
|
|
|
|
|
// Sanity check
|
|
|
|
if (!vd) {
|
|
|
|
errno = ENODEV;
|
|
|
|
return NULL;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// You cannot link between devices
|
2010-02-09 11:59:55 +01:00
|
|
|
if(target) {
|
|
|
|
if(vd != ntfsGetVolume(target)) {
|
2009-12-19 15:06:57 +01:00
|
|
|
errno = EXDEV;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the actual paths of the entry
|
|
|
|
path = ntfsRealPath(path);
|
|
|
|
target = ntfsRealPath(target);
|
2010-02-01 00:14:14 +01:00
|
|
|
if (!path) {
|
2009-12-19 15:06:57 +01:00
|
|
|
errno = EINVAL;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lock
|
|
|
|
ntfsLock(vd);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Get the unicode name for the entry and find its parent directory
|
|
|
|
// TODO: This looks horrible, clean it up
|
|
|
|
dir = strdup(path);
|
|
|
|
if (!dir) {
|
|
|
|
errno = EINVAL;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
name = strrchr(dir, '/');
|
|
|
|
if (name)
|
|
|
|
name++;
|
|
|
|
else
|
|
|
|
name = dir;
|
|
|
|
uname_len = ntfsLocalToUnicode(name, &uname);
|
|
|
|
if (uname_len < 0) {
|
|
|
|
errno = EINVAL;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
name = strrchr(dir, '/');
|
2010-02-09 11:59:55 +01:00
|
|
|
if(name)
|
|
|
|
{
|
2010-02-01 00:14:14 +01:00
|
|
|
name++;
|
|
|
|
name[0] = 0;
|
|
|
|
}
|
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Open the entries parent directory
|
|
|
|
dir_ni = ntfsOpenEntry(vd, dir);
|
|
|
|
if (!dir_ni) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the entry
|
|
|
|
switch (type) {
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Symbolic link
|
2010-02-09 11:59:55 +01:00
|
|
|
case S_IFLNK:
|
|
|
|
if (!target) {
|
|
|
|
errno = EINVAL;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
utarget_len = ntfsLocalToUnicode(target, &utarget);
|
|
|
|
if (utarget_len < 0) {
|
|
|
|
errno = EINVAL;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
ni = ntfs_create_symlink(dir_ni, 0, uname, uname_len, utarget, utarget_len);
|
|
|
|
break;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Directory or file
|
2010-02-09 11:59:55 +01:00
|
|
|
case S_IFDIR:
|
|
|
|
case S_IFREG:
|
|
|
|
ni = ntfs_create(dir_ni, 0, uname, uname_len, type);
|
|
|
|
break;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// If the entry was created
|
|
|
|
if (ni) {
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Mark the entry for archiving
|
|
|
|
ni->flags |= FILE_ATTR_ARCHIVE;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Mark the entry as dirty
|
|
|
|
NInoSetDirty(ni);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Sync the entry to disc
|
|
|
|
ntfsSync(vd, ni);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Update parent directories times
|
|
|
|
ntfsUpdateTimes(vd, dir_ni, NTFS_UPDATE_MCTIME);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
cleanup:
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
if(dir_ni)
|
2009-12-19 15:06:57 +01:00
|
|
|
ntfsCloseEntry(vd, dir_ni);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
if(utarget)
|
2009-12-19 15:06:57 +01:00
|
|
|
ntfs_free(utarget);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
if(uname)
|
2009-12-19 15:06:57 +01:00
|
|
|
ntfs_free(uname);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
if(dir)
|
2009-12-19 15:06:57 +01:00
|
|
|
ntfs_free(dir);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Unlock
|
|
|
|
ntfsUnlock(vd);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
return ni;
|
|
|
|
}
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
int ntfsLink (ntfs_vd *vd, const char *old_path, const char *new_path)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
ntfs_inode *dir_ni = NULL, *ni = NULL;
|
|
|
|
char *dir = NULL;
|
|
|
|
char *name = NULL;
|
|
|
|
ntfschar *uname = NULL;
|
|
|
|
int uname_len;
|
|
|
|
int res = 0;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Sanity check
|
|
|
|
if (!vd) {
|
|
|
|
errno = ENODEV;
|
|
|
|
return -1;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// You cannot link between devices
|
2010-02-09 11:59:55 +01:00
|
|
|
if(vd != ntfsGetVolume(new_path)) {
|
2009-12-19 15:06:57 +01:00
|
|
|
errno = EXDEV;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the actual paths of the entry
|
|
|
|
old_path = ntfsRealPath(old_path);
|
|
|
|
new_path = ntfsRealPath(new_path);
|
|
|
|
if (!old_path || !new_path) {
|
|
|
|
errno = EINVAL;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lock
|
|
|
|
ntfsLock(vd);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Get the unicode name for the entry and find its parent directory
|
|
|
|
// TODO: This looks horrible, clean it up
|
|
|
|
dir = strdup(new_path);
|
|
|
|
if (!dir) {
|
|
|
|
errno = EINVAL;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
name = strrchr(dir, '/');
|
|
|
|
if (name)
|
|
|
|
name++;
|
|
|
|
else
|
|
|
|
name = dir;
|
|
|
|
uname_len = ntfsLocalToUnicode(name, &uname);
|
|
|
|
if (uname_len < 0) {
|
|
|
|
errno = EINVAL;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
*name = 0;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Find the entry
|
|
|
|
ni = ntfsOpenEntry(vd, old_path);
|
|
|
|
if (!ni) {
|
|
|
|
errno = ENOENT;
|
|
|
|
res = -1;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Open the entries new parent directory
|
|
|
|
dir_ni = ntfsOpenEntry(vd, dir);
|
|
|
|
if (!dir_ni) {
|
|
|
|
errno = ENOENT;
|
|
|
|
res = -1;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Link the entry to its new parent
|
|
|
|
if (ntfs_link(ni, dir_ni, uname, uname_len)) {
|
|
|
|
res = -1;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Update entry times
|
|
|
|
ntfsUpdateTimes(vd, ni, NTFS_UPDATE_CTIME);
|
|
|
|
ntfsUpdateTimes(vd, dir_ni, NTFS_UPDATE_MCTIME);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Sync the entry to disc
|
|
|
|
ntfsSync(vd, ni);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
cleanup:
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
if(dir_ni)
|
2009-12-19 15:06:57 +01:00
|
|
|
ntfsCloseEntry(vd, dir_ni);
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
if(ni)
|
2009-12-19 15:06:57 +01:00
|
|
|
ntfsCloseEntry(vd, ni);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
if(uname)
|
2009-12-19 15:06:57 +01:00
|
|
|
ntfs_free(uname);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
if(dir)
|
2009-12-19 15:06:57 +01:00
|
|
|
ntfs_free(dir);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Unlock
|
|
|
|
ntfsUnlock(vd);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
int ntfsUnlink (ntfs_vd *vd, const char *path)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
ntfs_inode *dir_ni = NULL, *ni = NULL;
|
|
|
|
char *dir = NULL;
|
|
|
|
char *name = NULL;
|
|
|
|
ntfschar *uname = NULL;
|
|
|
|
int uname_len;
|
|
|
|
int res = 0;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Sanity check
|
|
|
|
if (!vd) {
|
|
|
|
errno = ENODEV;
|
|
|
|
return -1;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Get the actual path of the entry
|
|
|
|
path = ntfsRealPath(path);
|
|
|
|
if (!path) {
|
|
|
|
errno = EINVAL;
|
|
|
|
return -1;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Lock
|
|
|
|
ntfsLock(vd);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Get the unicode name for the entry and find its parent directory
|
|
|
|
// TODO: This looks horrible
|
|
|
|
dir = strdup(path);
|
|
|
|
if (!dir) {
|
|
|
|
errno = EINVAL;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
name = strrchr(dir, '/');
|
|
|
|
if (name)
|
|
|
|
name++;
|
|
|
|
else
|
|
|
|
name = dir;
|
|
|
|
uname_len = ntfsLocalToUnicode(name, &uname);
|
|
|
|
if (uname_len < 0) {
|
|
|
|
errno = EINVAL;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
name = strrchr(dir, '/');
|
2010-02-09 11:59:55 +01:00
|
|
|
if(name)
|
|
|
|
{
|
2010-02-01 00:14:14 +01:00
|
|
|
name++;
|
|
|
|
name[0] = 0;
|
|
|
|
}
|
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Find the entry
|
|
|
|
ni = ntfsOpenEntry(vd, path);
|
|
|
|
if (!ni) {
|
|
|
|
errno = ENOENT;
|
|
|
|
res = -1;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Open the entries parent directory
|
|
|
|
dir_ni = ntfsOpenEntry(vd, dir);
|
|
|
|
if (!dir_ni) {
|
|
|
|
errno = ENOENT;
|
|
|
|
res = -1;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Unlink the entry from its parent
|
|
|
|
if (ntfs_delete(vd->vol, path, ni, dir_ni, uname, uname_len)) {
|
|
|
|
res = -1;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Force the underlying device to sync
|
|
|
|
vd->dev->d_ops->sync(vd->dev);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// ntfs_delete() ALWAYS closes ni and dir_ni; so no need for us to anymore
|
|
|
|
dir_ni = ni = NULL;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
cleanup:
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
if(dir_ni)
|
2009-12-19 15:06:57 +01:00
|
|
|
ntfsCloseEntry(vd, dir_ni);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
if(ni)
|
2009-12-19 15:06:57 +01:00
|
|
|
ntfsCloseEntry(vd, ni);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
if(uname)
|
2009-12-19 15:06:57 +01:00
|
|
|
ntfs_free(uname);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
if(dir)
|
2009-12-19 15:06:57 +01:00
|
|
|
ntfs_free(dir);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Unlock
|
|
|
|
ntfsUnlock(vd);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
int ntfsSync (ntfs_vd *vd, ntfs_inode *ni)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
int res = 0;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Sanity check
|
|
|
|
if (!vd) {
|
|
|
|
errno = ENODEV;
|
|
|
|
return -1;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Sanity check
|
|
|
|
if (!ni) {
|
|
|
|
errno = ENOENT;
|
|
|
|
return -1;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Lock
|
|
|
|
ntfsLock(vd);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Sync the entry
|
|
|
|
res = ntfs_inode_sync(ni);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Force the underlying device to sync
|
2010-02-01 00:14:14 +01:00
|
|
|
vd->dev->d_ops->sync(vd->dev);
|
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Unlock
|
|
|
|
ntfsUnlock(vd);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
return res;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
}
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
int ntfsStat (ntfs_vd *vd, ntfs_inode *ni, struct stat *st)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
ntfs_attr *na = NULL;
|
|
|
|
int res = 0;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Sanity check
|
|
|
|
if (!vd) {
|
|
|
|
errno = ENODEV;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sanity check
|
|
|
|
if (!ni) {
|
|
|
|
errno = ENOENT;
|
|
|
|
return -1;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Short circuit cases were we don't actually have to do anything
|
|
|
|
if (!st)
|
|
|
|
return 0;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Lock
|
|
|
|
ntfsLock(vd);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Zero out the stat buffer
|
|
|
|
memset(st, 0, sizeof(struct stat));
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Is this entry a directory
|
|
|
|
if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
|
|
|
|
st->st_mode = S_IFDIR | (0777 & ~vd->dmask);
|
|
|
|
st->st_nlink = 1;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Open the directories index allocation table attribute
|
|
|
|
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);
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
// Else it must be a file
|
2009-12-19 15:06:57 +01:00
|
|
|
} else {
|
|
|
|
st->st_mode = S_IFREG | (0777 & ~vd->fmask);
|
|
|
|
st->st_size = ni->data_size;
|
|
|
|
st->st_blocks = (ni->allocated_size + 511) >> 9;
|
|
|
|
st->st_nlink = le16_to_cpu(ni->mrec->link_count);
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Fill in the generic entry stats
|
|
|
|
st->st_dev = vd->id;
|
|
|
|
st->st_uid = vd->uid;
|
|
|
|
st->st_gid = vd->gid;
|
|
|
|
st->st_ino = ni->mft_no;
|
|
|
|
st->st_atime = ni->last_access_time;
|
|
|
|
st->st_ctime = ni->last_mft_change_time;
|
|
|
|
st->st_mtime = ni->last_data_change_time;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Update entry times
|
|
|
|
ntfsUpdateTimes(vd, ni, NTFS_UPDATE_ATIME);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Unlock
|
|
|
|
ntfsUnlock(vd);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
void ntfsUpdateTimes (ntfs_vd *vd, ntfs_inode *ni, ntfs_time_update_flags mask)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
// Run the access time update strategy against the device driver settings first
|
|
|
|
if (vd && vd->atime == ATIME_DISABLED)
|
|
|
|
mask &= ~NTFS_UPDATE_ATIME;
|
|
|
|
|
|
|
|
// Update entry times
|
|
|
|
if (ni && mask)
|
|
|
|
ntfs_inode_update_times(ni, mask);
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
const char *ntfsRealPath (const char *path)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
// Sanity check
|
|
|
|
if (!path)
|
|
|
|
return NULL;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Move the path pointer to the start of the actual path
|
|
|
|
if (strchr(path, ':') != NULL) {
|
|
|
|
path = strchr(path, ':') + 1;
|
|
|
|
}
|
|
|
|
if (strchr(path, ':') != NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
int ntfsUnicodeToLocal (const ntfschar *ins, const int ins_len, char **outs, int outs_len)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
int len = 0;
|
|
|
|
int i;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Sanity check
|
|
|
|
if (!ins || !ins_len || !outs)
|
|
|
|
return 0;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Convert the unicode string to our current local
|
2010-02-01 00:14:14 +01:00
|
|
|
len = ntfs_ucstombs(ins, ins_len, outs, outs_len);
|
2009-12-19 15:06:57 +01:00
|
|
|
if (len == -1 && errno == EILSEQ) {
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// The string could not be converted to the current local,
|
|
|
|
// do it manually by replacing non-ASCII characters with underscores
|
|
|
|
if (!*outs || outs_len >= ins_len) {
|
|
|
|
if (!*outs) {
|
|
|
|
*outs = ntfs_alloc(ins_len + 1);
|
|
|
|
if (!*outs) {
|
|
|
|
errno = ENOMEM;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (i = 0; i < ins_len; i++) {
|
|
|
|
ntfschar uc = le16_to_cpu(ins[i]);
|
|
|
|
if (uc > 0xff)
|
|
|
|
uc = (ntfschar)'_';
|
|
|
|
*outs[i] = (char)uc;
|
|
|
|
}
|
|
|
|
*outs[ins_len] = (ntfschar)'\0';
|
|
|
|
len = ins_len;
|
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
}
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2010-02-09 11:59:55 +01:00
|
|
|
int ntfsLocalToUnicode (const char *ins, ntfschar **outs)
|
|
|
|
{
|
2009-12-19 15:06:57 +01:00
|
|
|
// Sanity check
|
|
|
|
if (!ins || !outs)
|
|
|
|
return 0;
|
2010-02-01 00:14:14 +01:00
|
|
|
|
2009-12-19 15:06:57 +01:00
|
|
|
// Convert the local string to unicode
|
|
|
|
return ntfs_mbstoucs(ins, outs);
|
|
|
|
}
|