1 /*
   2  * Copyright (c) 2001, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /* Access APIs for WinXP and above */
  27 #ifndef _WIN32_WINNT
  28 #define _WIN32_WINNT 0x0501
  29 #endif
  30 
  31 #include <assert.h>
  32 #include <stdio.h>
  33 #include <stdlib.h>
  34 #include <ctype.h>
  35 #include <direct.h>
  36 #include <windows.h>
  37 #include <io.h>
  38 
  39 #include "jni.h"
  40 #include "io_util.h"
  41 #include "jlong.h"
  42 #include "io_util_md.h"
  43 #include "dirent_md.h"
  44 #include "java_io_FileSystem.h"
  45 
  46 #define MAX_PATH_LENGTH 1024
  47 
  48 static struct {
  49     jfieldID path;
  50 } ids;
  51 
  52 /**
  53  * GetFinalPathNameByHandle is available on Windows Vista and newer
  54  */
  55 typedef BOOL (WINAPI* GetFinalPathNameByHandleProc) (HANDLE, LPWSTR, DWORD, DWORD);
  56 static GetFinalPathNameByHandleProc GetFinalPathNameByHandle_func;
  57 
  58 JNIEXPORT void JNICALL
  59 Java_java_io_WinNTFileSystem_initIDs(JNIEnv *env, jclass cls)
  60 {
  61     HMODULE handle;
  62     jclass fileClass = (*env)->FindClass(env, "java/io/File");
  63     if (!fileClass) return;
  64     ids.path =
  65              (*env)->GetFieldID(env, fileClass, "path", "Ljava/lang/String;");
  66 
  67     // GetFinalPathNameByHandle requires Windows Vista or newer
  68     if (GetModuleHandleExW((GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
  69                             GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT),
  70                            (LPCWSTR)&CreateFileW, &handle) != 0)
  71     {
  72         GetFinalPathNameByHandle_func = (GetFinalPathNameByHandleProc)
  73             GetProcAddress(handle, "GetFinalPathNameByHandleW");
  74     }
  75 }
  76 
  77 /* -- Path operations -- */
  78 
  79 extern int wcanonicalize(const WCHAR *path, WCHAR *out, int len);
  80 extern int wcanonicalizeWithPrefix(const WCHAR *canonicalPrefix, const WCHAR *pathWithCanonicalPrefix, WCHAR *out, int len);
  81 
  82 /**
  83  * Retrieves the fully resolved (final) path for the given path or NULL
  84  * if the function fails.
  85  */
  86 static WCHAR* getFinalPath(const WCHAR *path)
  87 {
  88     HANDLE h;
  89     WCHAR *result;
  90     DWORD error;
  91 
  92     /* Need Windows Vista or newer to get the final path */
  93     if (GetFinalPathNameByHandle_func == NULL)
  94         return NULL;
  95 
  96     h = CreateFileW(path,
  97                     FILE_READ_ATTRIBUTES,
  98                     FILE_SHARE_DELETE |
  99                         FILE_SHARE_READ | FILE_SHARE_WRITE,
 100                     NULL,
 101                     OPEN_EXISTING,
 102                     FILE_FLAG_BACKUP_SEMANTICS,
 103                     NULL);
 104     if (h == INVALID_HANDLE_VALUE)
 105         return NULL;
 106 
 107     /**
 108      * Allocate a buffer for the resolved path. For a long path we may need
 109      * to allocate a larger buffer.
 110      */
 111     result = (WCHAR*)malloc(MAX_PATH * sizeof(WCHAR));
 112     if (result != NULL) {
 113         DWORD len = (*GetFinalPathNameByHandle_func)(h, result, MAX_PATH, 0);
 114         if (len >= MAX_PATH) {
 115             /* retry with a buffer of the right size */
 116             WCHAR* newResult = (WCHAR*)realloc(result, (len+1) * sizeof(WCHAR));
 117             if (newResult != NULL) {
 118                 result = newResult;
 119                 len = (*GetFinalPathNameByHandle_func)(h, result, len, 0);
 120             } else {
 121                 len = 0;
 122             }
 123         }
 124 
 125         if (len > 0) {
 126             /**
 127              * Strip prefix (should be \\?\ or \\?\UNC)
 128              */
 129             if (result[0] == L'\\' && result[1] == L'\\' &&
 130                 result[2] == L'?' && result[3] == L'\\')
 131             {
 132                 int isUnc = (result[4] == L'U' &&
 133                              result[5] == L'N' &&
 134                              result[6] == L'C');
 135                 int prefixLen = (isUnc) ? 7 : 4;
 136                 /* actual result length (includes terminator) */
 137                 int resultLen = len - prefixLen + (isUnc ? 1 : 0) + 1;
 138 
 139                 /* copy result without prefix into new buffer */
 140                 WCHAR *tmp = (WCHAR*)malloc(resultLen * sizeof(WCHAR));
 141                 if (tmp == NULL) {
 142                     len = 0;
 143                 } else {
 144                     WCHAR *p = result;
 145                     p += prefixLen;
 146                     if (isUnc) {
 147                         WCHAR *p2 = tmp;
 148                         p2[0] = L'\\';
 149                         p2++;
 150                         wcscpy(p2, p);
 151                     } else {
 152                         wcscpy(tmp, p);
 153                     }
 154                     free(result);
 155                     result = tmp;
 156                 }
 157             }
 158         }
 159 
 160         /* unable to get final path */
 161         if (len == 0 && result != NULL) {
 162             free(result);
 163             result = NULL;
 164         }
 165     }
 166 
 167     error = GetLastError();
 168     if (CloseHandle(h))
 169         SetLastError(error);
 170     return result;
 171 }
 172 
 173 /**
 174  * Retrieves file information for the specified file. If the file is
 175  * symbolic link then the information on fully resolved target is
 176  * returned.
 177  */
 178 static BOOL getFileInformation(const WCHAR *path,
 179                                BY_HANDLE_FILE_INFORMATION *finfo)
 180 {
 181     BOOL result;
 182     DWORD error;
 183     HANDLE h = CreateFileW(path,
 184                            FILE_READ_ATTRIBUTES,
 185                            FILE_SHARE_DELETE |
 186                                FILE_SHARE_READ | FILE_SHARE_WRITE,
 187                            NULL,
 188                            OPEN_EXISTING,
 189                            FILE_FLAG_BACKUP_SEMANTICS,
 190                            NULL);
 191     if (h == INVALID_HANDLE_VALUE)
 192         return FALSE;
 193     result = GetFileInformationByHandle(h, finfo);
 194     error = GetLastError();
 195     if (CloseHandle(h))
 196         SetLastError(error);
 197     return result;
 198 }
 199 
 200 /**
 201  * If the given attributes are the attributes of a reparse point, then
 202  * read and return the attributes of the final target.
 203  */
 204 DWORD getFinalAttributesIfReparsePoint(WCHAR *path, DWORD a)
 205 {
 206     if ((a != INVALID_FILE_ATTRIBUTES) &&
 207         ((a & FILE_ATTRIBUTE_REPARSE_POINT) != 0))
 208     {
 209         BY_HANDLE_FILE_INFORMATION finfo;
 210         BOOL res = getFileInformation(path, &finfo);
 211         a = (res) ? finfo.dwFileAttributes : INVALID_FILE_ATTRIBUTES;
 212     }
 213     return a;
 214 }
 215 
 216 JNIEXPORT jstring JNICALL
 217 Java_java_io_WinNTFileSystem_canonicalize0(JNIEnv *env, jobject this,
 218                                            jstring pathname)
 219 {
 220     jstring rv = NULL;
 221     WCHAR canonicalPath[MAX_PATH_LENGTH];
 222 
 223     WITH_UNICODE_STRING(env, pathname, path) {
 224         /*we estimate the max length of memory needed as
 225           "currentDir. length + pathname.length"
 226          */
 227         int len = (int)wcslen(path);
 228         len += currentDirLength(path, len);
 229         if (len  > MAX_PATH_LENGTH - 1) {
 230             WCHAR *cp = (WCHAR*)malloc(len * sizeof(WCHAR));
 231             if (cp != NULL) {
 232                 if (wcanonicalize(path, cp, len) >= 0) {
 233                     rv = (*env)->NewString(env, cp, (jsize)wcslen(cp));
 234                 }
 235                 free(cp);
 236             }
 237         } else
 238         if (wcanonicalize(path, canonicalPath, MAX_PATH_LENGTH) >= 0) {
 239             rv = (*env)->NewString(env, canonicalPath, (jsize)wcslen(canonicalPath));
 240         }
 241     } END_UNICODE_STRING(env, path);
 242     if (rv == NULL) {
 243         JNU_ThrowIOExceptionWithLastError(env, "Bad pathname");
 244     }
 245     return rv;
 246 }
 247 
 248 
 249 JNIEXPORT jstring JNICALL
 250 Java_java_io_WinNTFileSystem_canonicalizeWithPrefix0(JNIEnv *env, jobject this,
 251                                                      jstring canonicalPrefixString,
 252                                                      jstring pathWithCanonicalPrefixString)
 253 {
 254     jstring rv = NULL;
 255     WCHAR canonicalPath[MAX_PATH_LENGTH];
 256     WITH_UNICODE_STRING(env, canonicalPrefixString, canonicalPrefix) {
 257         WITH_UNICODE_STRING(env, pathWithCanonicalPrefixString, pathWithCanonicalPrefix) {
 258             int len = (int)wcslen(canonicalPrefix) + MAX_PATH;
 259             if (len > MAX_PATH_LENGTH) {
 260                 WCHAR *cp = (WCHAR*)malloc(len * sizeof(WCHAR));
 261                 if (cp != NULL) {
 262                     if (wcanonicalizeWithPrefix(canonicalPrefix,
 263                                                 pathWithCanonicalPrefix,
 264                                                 cp, len) >= 0) {
 265                       rv = (*env)->NewString(env, cp, (jsize)wcslen(cp));
 266                     }
 267                     free(cp);
 268                 }
 269             } else
 270             if (wcanonicalizeWithPrefix(canonicalPrefix,
 271                                         pathWithCanonicalPrefix,
 272                                         canonicalPath, MAX_PATH_LENGTH) >= 0) {
 273                 rv = (*env)->NewString(env, canonicalPath, (jsize)wcslen(canonicalPath));
 274             }
 275         } END_UNICODE_STRING(env, pathWithCanonicalPrefix);
 276     } END_UNICODE_STRING(env, canonicalPrefix);
 277     if (rv == NULL) {
 278         JNU_ThrowIOExceptionWithLastError(env, "Bad pathname");
 279     }
 280     return rv;
 281 }
 282 
 283 /* -- Attribute accessors -- */
 284 
 285 /* Check whether or not the file name in "path" is a Windows reserved
 286    device name (CON, PRN, AUX, NUL, COM[1-9], LPT[1-9]) based on the
 287    returned result from GetFullPathName, which should be in thr form of
 288    "\\.\[ReservedDeviceName]" if the path represents a reserved device
 289    name.
 290    Note1: GetFullPathName doesn't think "CLOCK$" (which is no longer
 291    important anyway) is a device name, so we don't check it here.
 292    GetFileAttributesEx will catch it later by returning 0 on NT/XP/
 293    200X.
 294 
 295    Note2: Theoretically the implementation could just lookup the table
 296    below linearly if the first 4 characters of the fullpath returned
 297    from GetFullPathName are "\\.\". The current implementation should
 298    achieve the same result. If Microsoft add more names into their
 299    reserved device name repository in the future, which probably will
 300    never happen, we will need to revisit the lookup implementation.
 301 
 302 static WCHAR* ReservedDEviceNames[] = {
 303     L"CON", L"PRN", L"AUX", L"NUL",
 304     L"COM1", L"COM2", L"COM3", L"COM4", L"COM5", L"COM6", L"COM7", L"COM8", L"COM9",
 305     L"LPT1", L"LPT2", L"LPT3", L"LPT4", L"LPT5", L"LPT6", L"LPT7", L"LPT8", L"LPT9",
 306     L"CLOCK$"
 307 };
 308  */
 309 
 310 static BOOL isReservedDeviceNameW(WCHAR* path) {
 311 #define BUFSIZE 9
 312     WCHAR buf[BUFSIZE];
 313     WCHAR *lpf = NULL;
 314     DWORD retLen = GetFullPathNameW(path,
 315                                    BUFSIZE,
 316                                    buf,
 317                                    &lpf);
 318     if ((retLen == BUFSIZE - 1 || retLen == BUFSIZE - 2) &&
 319         buf[0] == L'\\' && buf[1] == L'\\' &&
 320         buf[2] == L'.' && buf[3] == L'\\') {
 321         WCHAR* dname = _wcsupr(buf + 4);
 322         if (wcscmp(dname, L"CON") == 0 ||
 323             wcscmp(dname, L"PRN") == 0 ||
 324             wcscmp(dname, L"AUX") == 0 ||
 325             wcscmp(dname, L"NUL") == 0)
 326             return TRUE;
 327         if ((wcsncmp(dname, L"COM", 3) == 0 ||
 328              wcsncmp(dname, L"LPT", 3) == 0) &&
 329             dname[3] - L'0' > 0 &&
 330             dname[3] - L'0' <= 9)
 331             return TRUE;
 332     }
 333     return FALSE;
 334 }
 335 
 336 DWORD getFileAttributesWithSpecialCase(WCHAR *path)
 337 {
 338     DWORD attr = INVALID_FILE_ATTRIBUTES;
 339 
 340     WIN32_FILE_ATTRIBUTE_DATA wfad;
 341     WIN32_FIND_DATAW wfd;
 342     HANDLE h;
 343 
 344     if (GetFileAttributesExW(path, GetFileExInfoStandard, &wfad)) {
 345                 attr = getFinalAttributesIfReparsePoint(path, wfad.dwFileAttributes);
 346     } else { /* pagefile.sys is a special case */
 347         if ((h = FindFirstFileW(path, &wfd)) != INVALID_HANDLE_VALUE) {
 348             attr = wfd.dwFileAttributes;
 349             CloseHandle(h);
 350         }
 351     }
 352     return attr;
 353 }
 354 
 355 JNIEXPORT jint JNICALL
 356 Java_java_io_WinNTFileSystem_getBooleanAttributes(JNIEnv *env, jobject this,
 357                                                   jobject file)
 358 {
 359     jint rv = 0;
 360     jint pathlen;
 361 
 362     WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
 363     if (pathbuf == NULL)
 364         return rv;
 365     if (!isReservedDeviceNameW(pathbuf)) {
 366         DWORD a = getFileAttributesWithSpecialCase(pathbuf);
 367         if (a != INVALID_FILE_ATTRIBUTES) {
 368             rv = (java_io_FileSystem_BA_EXISTS
 369                 | ((a & FILE_ATTRIBUTE_DIRECTORY)
 370                     ? java_io_FileSystem_BA_DIRECTORY
 371                     : java_io_FileSystem_BA_REGULAR)
 372                 | ((a & FILE_ATTRIBUTE_HIDDEN)
 373                     ? java_io_FileSystem_BA_HIDDEN : 0));
 374         }
 375     }
 376     free(pathbuf);
 377     return rv;
 378 }
 379 
 380 
 381 JNIEXPORT jboolean
 382 JNICALL Java_java_io_WinNTFileSystem_checkAccess(JNIEnv *env, jobject this,
 383                                                  jobject file, jint access)
 384 {
 385     DWORD attr;
 386     WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
 387     if (pathbuf == NULL)
 388         return JNI_FALSE;
 389     attr = GetFileAttributesW(pathbuf);
 390     attr = getFinalAttributesIfReparsePoint(pathbuf, attr);
 391     free(pathbuf);
 392     if (attr == INVALID_FILE_ATTRIBUTES)
 393         return JNI_FALSE;
 394     switch (access) {
 395     case java_io_FileSystem_ACCESS_READ:
 396     case java_io_FileSystem_ACCESS_EXECUTE:
 397         return JNI_TRUE;
 398     case java_io_FileSystem_ACCESS_WRITE:
 399         /* Read-only attribute ignored on directories */
 400         if ((attr & FILE_ATTRIBUTE_DIRECTORY) ||
 401             (attr & FILE_ATTRIBUTE_READONLY) == 0)
 402             return JNI_TRUE;
 403         else
 404             return JNI_FALSE;
 405     default:
 406         assert(0);
 407         return JNI_FALSE;
 408     }
 409 }
 410 
 411 JNIEXPORT jboolean JNICALL
 412 Java_java_io_WinNTFileSystem_setPermission(JNIEnv *env, jobject this,
 413                                            jobject file,
 414                                            jint access,
 415                                            jboolean enable,
 416                                            jboolean owneronly)
 417 {
 418     jboolean rv = JNI_FALSE;
 419     WCHAR *pathbuf;
 420     DWORD a;
 421     if (access == java_io_FileSystem_ACCESS_READ ||
 422         access == java_io_FileSystem_ACCESS_EXECUTE) {
 423         return enable;
 424     }
 425     pathbuf = fileToNTPath(env, file, ids.path);
 426     if (pathbuf == NULL)
 427         return JNI_FALSE;
 428     a = GetFileAttributesW(pathbuf);
 429 
 430     /* if reparse point, get final target */
 431     if ((a != INVALID_FILE_ATTRIBUTES) &&
 432         ((a & FILE_ATTRIBUTE_REPARSE_POINT) != 0))
 433     {
 434         WCHAR *fp = getFinalPath(pathbuf);
 435         if (fp == NULL) {
 436             a = INVALID_FILE_ATTRIBUTES;
 437         } else {
 438             free(pathbuf);
 439             pathbuf = fp;
 440             a = GetFileAttributesW(pathbuf);
 441         }
 442     }
 443     if ((a != INVALID_FILE_ATTRIBUTES) &&
 444         ((a & FILE_ATTRIBUTE_DIRECTORY) == 0))
 445     {
 446         if (enable)
 447             a =  a & ~FILE_ATTRIBUTE_READONLY;
 448         else
 449             a =  a | FILE_ATTRIBUTE_READONLY;
 450         if (SetFileAttributesW(pathbuf, a))
 451             rv = JNI_TRUE;
 452     }
 453     free(pathbuf);
 454     return rv;
 455 }
 456 
 457 JNIEXPORT jlong JNICALL
 458 Java_java_io_WinNTFileSystem_getLastModifiedTime(JNIEnv *env, jobject this,
 459                                                  jobject file)
 460 {
 461     jlong rv = 0;
 462     LARGE_INTEGER modTime;
 463     FILETIME t;
 464     HANDLE h;
 465     WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
 466     if (pathbuf == NULL)
 467         return rv;
 468     h = CreateFileW(pathbuf,
 469                     /* Device query access */
 470                     0,
 471                     /* Share it */
 472                     FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
 473                     /* No security attributes */
 474                     NULL,
 475                     /* Open existing or fail */
 476                     OPEN_EXISTING,
 477                     /* Backup semantics for directories */
 478                     FILE_FLAG_BACKUP_SEMANTICS,
 479                     /* No template file */
 480                     NULL);
 481     if (h != INVALID_HANDLE_VALUE) {
 482         if (GetFileTime(h, NULL, NULL, &t)) {
 483             modTime.LowPart = (DWORD) t.dwLowDateTime;
 484             modTime.HighPart = (LONG) t.dwHighDateTime;
 485             rv = modTime.QuadPart / 10000;
 486             rv -= 11644473600000;
 487         }
 488         CloseHandle(h);
 489     }
 490     free(pathbuf);
 491     return rv;
 492 }
 493 
 494 JNIEXPORT jlong JNICALL
 495 Java_java_io_WinNTFileSystem_getLength(JNIEnv *env, jobject this, jobject file)
 496 {
 497     jlong rv = 0;
 498     WIN32_FILE_ATTRIBUTE_DATA wfad;
 499     WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
 500     if (pathbuf == NULL)
 501         return rv;
 502     if (GetFileAttributesExW(pathbuf,
 503                              GetFileExInfoStandard,
 504                              &wfad)) {
 505         if ((wfad.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) {
 506             rv = wfad.nFileSizeHigh * ((jlong)MAXDWORD + 1) + wfad.nFileSizeLow;
 507         } else {
 508             /* file is a reparse point so read attributes of final target */
 509             BY_HANDLE_FILE_INFORMATION finfo;
 510             if (getFileInformation(pathbuf, &finfo)) {
 511                 rv = finfo.nFileSizeHigh * ((jlong)MAXDWORD + 1) +
 512                     finfo.nFileSizeLow;
 513             }
 514         }
 515     } else {
 516         if (GetLastError() == ERROR_SHARING_VIOLATION) {
 517             /* The error is "share violation", which means the file/dir
 518                must exists. Try _wstati64, we know this at least works
 519                for pagefile.sys and hiberfil.sys.
 520             */
 521             struct _stati64 sb;
 522             if (_wstati64(pathbuf, &sb) == 0) {
 523                 rv = sb.st_size;
 524             }
 525         }
 526     }
 527     free(pathbuf);
 528     return rv;
 529 }
 530 
 531 /* -- File operations -- */
 532 
 533 JNIEXPORT jboolean JNICALL
 534 Java_java_io_WinNTFileSystem_createFileExclusively(JNIEnv *env, jclass cls,
 535                                                    jstring path)
 536 {
 537     HANDLE h = NULL;
 538     WCHAR *pathbuf = pathToNTPath(env, path, JNI_FALSE);
 539     if (pathbuf == NULL)
 540         return JNI_FALSE;
 541     h = CreateFileW(
 542         pathbuf,                              /* Wide char path name */
 543         GENERIC_READ | GENERIC_WRITE,         /* Read and write permission */
 544         FILE_SHARE_READ | FILE_SHARE_WRITE,   /* File sharing flags */
 545         NULL,                                 /* Security attributes */
 546         CREATE_NEW,                           /* creation disposition */
 547         FILE_ATTRIBUTE_NORMAL |
 548             FILE_FLAG_OPEN_REPARSE_POINT,     /* flags and attributes */
 549         NULL);
 550 
 551     if (h == INVALID_HANDLE_VALUE) {
 552         DWORD error = GetLastError();
 553         if ((error != ERROR_FILE_EXISTS) && (error != ERROR_ALREADY_EXISTS)) {
 554             // return false rather than throwing an exception when there is
 555             // an existing file.
 556             DWORD a = GetFileAttributesW(pathbuf);
 557             if (a == INVALID_FILE_ATTRIBUTES) {
 558                 SetLastError(error);
 559                 JNU_ThrowIOExceptionWithLastError(env, "Could not open file");
 560             }
 561          }
 562          free(pathbuf);
 563          return JNI_FALSE;
 564         }
 565     free(pathbuf);
 566     CloseHandle(h);
 567     return JNI_TRUE;
 568 }
 569 
 570 static int
 571 removeFileOrDirectory(const jchar *path)
 572 {
 573     /* Returns 0 on success */
 574     DWORD a;
 575 
 576     SetFileAttributesW(path, FILE_ATTRIBUTE_NORMAL);
 577     a = GetFileAttributesW(path);
 578     if (a == INVALID_FILE_ATTRIBUTES) {
 579         return 1;
 580     } else if (a & FILE_ATTRIBUTE_DIRECTORY) {
 581         return !RemoveDirectoryW(path);
 582     } else {
 583         return !DeleteFileW(path);
 584     }
 585 }
 586 
 587 JNIEXPORT jboolean JNICALL
 588 Java_java_io_WinNTFileSystem_delete0(JNIEnv *env, jobject this, jobject file)
 589 {
 590     jboolean rv = JNI_FALSE;
 591     WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
 592     if (pathbuf == NULL) {
 593         return JNI_FALSE;
 594     }
 595     if (removeFileOrDirectory(pathbuf) == 0) {
 596         rv = JNI_TRUE;
 597     }
 598     free(pathbuf);
 599     return rv;
 600 }
 601 
 602 JNIEXPORT jobjectArray JNICALL
 603 Java_java_io_WinNTFileSystem_list(JNIEnv *env, jobject this, jobject file)
 604 {
 605     WCHAR *search_path;
 606     HANDLE handle;
 607     WIN32_FIND_DATAW find_data;
 608     int len, maxlen;
 609     jobjectArray rv, old;
 610     DWORD fattr;
 611     jstring name;
 612 
 613     WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
 614     if (pathbuf == NULL)
 615         return NULL;
 616     search_path = (WCHAR*)malloc(2*wcslen(pathbuf) + 6);
 617     if (search_path == 0) {
 618         free (pathbuf);
 619         errno = ENOMEM;
 620         return NULL;
 621     }
 622     wcscpy(search_path, pathbuf);
 623     free(pathbuf);
 624     fattr = GetFileAttributesW(search_path);
 625     if (fattr == INVALID_FILE_ATTRIBUTES) {
 626         free(search_path);
 627         return NULL;
 628     } else if ((fattr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
 629         free(search_path);
 630         return NULL;
 631     }
 632 
 633     /* Remove trailing space chars from directory name */
 634     len = (int)wcslen(search_path);
 635     while (search_path[len-1] == ' ') {
 636         len--;
 637     }
 638     search_path[len] = 0;
 639 
 640     /* Append "*", or possibly "\\*", to path */
 641     if ((search_path[0] == L'\\' && search_path[1] == L'\0') ||
 642         (search_path[1] == L':'
 643         && (search_path[2] == L'\0'
 644         || (search_path[2] == L'\\' && search_path[3] == L'\0')))) {
 645         /* No '\\' needed for cases like "\" or "Z:" or "Z:\" */
 646         wcscat(search_path, L"*");
 647     } else {
 648         wcscat(search_path, L"\\*");
 649     }
 650 
 651     /* Open handle to the first file */
 652     handle = FindFirstFileW(search_path, &find_data);
 653     free(search_path);
 654     if (handle == INVALID_HANDLE_VALUE) {
 655         if (GetLastError() != ERROR_FILE_NOT_FOUND) {
 656             // error
 657             return NULL;
 658         } else {
 659             // No files found - return an empty array
 660             rv = (*env)->NewObjectArray(env, 0, JNU_ClassString(env), NULL);
 661             return rv;
 662         }
 663     }
 664 
 665     /* Allocate an initial String array */
 666     len = 0;
 667     maxlen = 16;
 668     rv = (*env)->NewObjectArray(env, maxlen, JNU_ClassString(env), NULL);
 669     if (rv == NULL) // Couldn't allocate an array
 670         return NULL;
 671     /* Scan the directory */
 672     do {
 673         if (!wcscmp(find_data.cFileName, L".")
 674                                 || !wcscmp(find_data.cFileName, L".."))
 675            continue;
 676         name = (*env)->NewString(env, find_data.cFileName,
 677                                  (jsize)wcslen(find_data.cFileName));
 678         if (name == NULL)
 679             return NULL; // error;
 680         if (len == maxlen) {
 681             old = rv;
 682             rv = (*env)->NewObjectArray(env, maxlen <<= 1,
 683                                             JNU_ClassString(env), NULL);
 684             if ( rv == NULL
 685                          || JNU_CopyObjectArray(env, rv, old, len) < 0)
 686                 return NULL; // error
 687             (*env)->DeleteLocalRef(env, old);
 688         }
 689         (*env)->SetObjectArrayElement(env, rv, len++, name);
 690         (*env)->DeleteLocalRef(env, name);
 691 
 692     } while (FindNextFileW(handle, &find_data));
 693 
 694     if (GetLastError() != ERROR_NO_MORE_FILES)
 695         return NULL; // error
 696     FindClose(handle);
 697 
 698     /* Copy the final results into an appropriately-sized array */
 699     old = rv;
 700     rv = (*env)->NewObjectArray(env, len, JNU_ClassString(env), NULL);
 701     if (rv == NULL)
 702         return NULL; /* error */
 703     if (JNU_CopyObjectArray(env, rv, old, len) < 0)
 704         return NULL; /* error */
 705     return rv;
 706 }
 707 
 708 
 709 JNIEXPORT jboolean JNICALL
 710 Java_java_io_WinNTFileSystem_createDirectory(JNIEnv *env, jobject this,
 711                                              jobject file)
 712 {
 713     BOOL h = FALSE;
 714     WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
 715     if (pathbuf == NULL) {
 716         /* Exception is pending */
 717         return JNI_FALSE;
 718     }
 719     h = CreateDirectoryW(pathbuf, NULL);
 720     free(pathbuf);
 721 
 722     if (h == 0) {
 723         return JNI_FALSE;
 724     }
 725 
 726     return JNI_TRUE;
 727 }
 728 
 729 
 730 JNIEXPORT jboolean JNICALL
 731 Java_java_io_WinNTFileSystem_rename0(JNIEnv *env, jobject this, jobject from,
 732                                      jobject to)
 733 {
 734 
 735     jboolean rv = JNI_FALSE;
 736     WCHAR *frompath = fileToNTPath(env, from, ids.path);
 737     WCHAR *topath = fileToNTPath(env, to, ids.path);
 738     if (frompath == NULL || topath == NULL)
 739         return JNI_FALSE;
 740     if (_wrename(frompath, topath) == 0) {
 741         rv = JNI_TRUE;
 742     }
 743     free(frompath);
 744     free(topath);
 745     return rv;
 746 }
 747 
 748 
 749 JNIEXPORT jboolean JNICALL
 750 Java_java_io_WinNTFileSystem_setLastModifiedTime(JNIEnv *env, jobject this,
 751                                                  jobject file, jlong time)
 752 {
 753     jboolean rv = JNI_FALSE;
 754     WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
 755     HANDLE h;
 756     if (pathbuf == NULL)
 757         return JNI_FALSE;
 758     h = CreateFileW(pathbuf,
 759                     FILE_WRITE_ATTRIBUTES,
 760                     FILE_SHARE_READ | FILE_SHARE_WRITE,
 761                     NULL,
 762                     OPEN_EXISTING,
 763                     FILE_FLAG_BACKUP_SEMANTICS,
 764                     0);
 765     if (h != INVALID_HANDLE_VALUE) {
 766         LARGE_INTEGER modTime;
 767         FILETIME t;
 768         modTime.QuadPart = (time + 11644473600000L) * 10000L;
 769         t.dwLowDateTime = (DWORD)modTime.LowPart;
 770         t.dwHighDateTime = (DWORD)modTime.HighPart;
 771         if (SetFileTime(h, NULL, NULL, &t)) {
 772             rv = JNI_TRUE;
 773         }
 774         CloseHandle(h);
 775     }
 776     free(pathbuf);
 777 
 778     return rv;
 779 }
 780 
 781 
 782 JNIEXPORT jboolean JNICALL
 783 Java_java_io_WinNTFileSystem_setReadOnly(JNIEnv *env, jobject this,
 784                                          jobject file)
 785 {
 786     jboolean rv = JNI_FALSE;
 787     DWORD a;
 788     WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
 789     if (pathbuf == NULL)
 790         return JNI_FALSE;
 791     a = GetFileAttributesW(pathbuf);
 792 
 793     /* if reparse point, get final target */
 794     if ((a != INVALID_FILE_ATTRIBUTES) &&
 795         ((a & FILE_ATTRIBUTE_REPARSE_POINT) != 0))
 796     {
 797         WCHAR *fp = getFinalPath(pathbuf);
 798         if (fp == NULL) {
 799             a = INVALID_FILE_ATTRIBUTES;
 800         } else {
 801             free(pathbuf);
 802             pathbuf = fp;
 803             a = GetFileAttributesW(pathbuf);
 804         }
 805     }
 806 
 807     if ((a != INVALID_FILE_ATTRIBUTES) &&
 808         ((a & FILE_ATTRIBUTE_DIRECTORY) == 0)) {
 809         if (SetFileAttributesW(pathbuf, a | FILE_ATTRIBUTE_READONLY))
 810             rv = JNI_TRUE;
 811     }
 812     free(pathbuf);
 813     return rv;
 814 }
 815 
 816 /* -- Filesystem interface -- */
 817 
 818 
 819 JNIEXPORT jobject JNICALL
 820 Java_java_io_WinNTFileSystem_getDriveDirectory(JNIEnv *env, jobject this,
 821                                                jint drive)
 822 {
 823     jstring ret = NULL;
 824     jchar *p = currentDir(drive);
 825     jchar *pf = p;
 826     if (p == NULL) return NULL;
 827     if (iswalpha(*p) && (p[1] == L':')) p += 2;
 828     ret = (*env)->NewString(env, p, (jsize)wcslen(p));
 829     free (pf);
 830     return ret;
 831 }
 832 
 833 JNIEXPORT jint JNICALL
 834 Java_java_io_WinNTFileSystem_listRoots0(JNIEnv *env, jclass ignored)
 835 {
 836     return GetLogicalDrives();
 837 }
 838 
 839 JNIEXPORT jlong JNICALL
 840 Java_java_io_WinNTFileSystem_getSpace0(JNIEnv *env, jobject this,
 841                                        jobject file, jint t)
 842 {
 843     WCHAR volname[MAX_PATH_LENGTH + 1];
 844     jlong rv = 0L;
 845     WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
 846 
 847     if (GetVolumePathNameW(pathbuf, volname, MAX_PATH_LENGTH)) {
 848         ULARGE_INTEGER totalSpace, freeSpace, usableSpace;
 849         if (GetDiskFreeSpaceExW(volname, &usableSpace, &totalSpace, &freeSpace)) {
 850             switch(t) {
 851             case java_io_FileSystem_SPACE_TOTAL:
 852                 rv = long_to_jlong(totalSpace.QuadPart);
 853                 break;
 854             case java_io_FileSystem_SPACE_FREE:
 855                 rv = long_to_jlong(freeSpace.QuadPart);
 856                 break;
 857             case java_io_FileSystem_SPACE_USABLE:
 858                 rv = long_to_jlong(usableSpace.QuadPart);
 859                 break;
 860             default:
 861                 assert(0);
 862             }
 863         }
 864     }
 865 
 866     free(pathbuf);
 867     return rv;
 868 }