@@ -87,7 +87,19 @@ wchar_t* add_prefix(wchar_t* path, int path_len, wchar_t* prefix) {
87
87
int str_len = path_len + prefix_len;
88
88
wchar_t * str = (wchar_t *)malloc (sizeof (wchar_t ) * (str_len + 1 ));
89
89
wcscpy_s (str, str_len + 1 , prefix);
90
- wcscat_s (str, str_len + 1 , path);
90
+ wcsncat_s (str, str_len + 1 , path, path_len);
91
+ return str;
92
+ }
93
+
94
+ //
95
+ // Returns a UTF-16 string that is the concatenation of |path| and |suffix|.
96
+ //
97
+ wchar_t * add_suffix (wchar_t * path, int path_len, wchar_t * suffix) {
98
+ int suffix_len = wcslen (suffix);
99
+ int str_len = path_len + suffix_len;
100
+ wchar_t * str = (wchar_t *)malloc (sizeof (wchar_t ) * (str_len + 1 ));
101
+ wcsncpy_s (str, str_len + 1 , path, path_len);
102
+ wcscat_s (str, str_len + 1 , suffix);
91
103
return str;
92
104
}
93
105
@@ -127,6 +139,125 @@ wchar_t* java_to_wchar_path(JNIEnv *env, jstring string, jobject result) {
127
139
}
128
140
}
129
141
142
+ //
143
+ // Returns 'true' if a file, given its attributes, is a Windows Symbolic Link.
144
+ //
145
+ bool is_file_symlink (DWORD dwFileAttributes, DWORD reparseTagData) {
146
+ //
147
+ // See https://docs.microsoft.com/en-us/windows/desktop/fileio/reparse-point-tags
148
+ // IO_REPARSE_TAG_SYMLINK (0xA000000C)
149
+ //
150
+ return
151
+ ((dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT) &&
152
+ (reparseTagData == IO_REPARSE_TAG_SYMLINK);
153
+ }
154
+
155
+ jlong lastModifiedNanos (FILETIME* time) {
156
+ return ((jlong)time->dwHighDateTime << 32 ) | time->dwLowDateTime ;
157
+ }
158
+
159
+ jlong lastModifiedNanos (LARGE_INTEGER* time) {
160
+ return ((jlong)time->HighPart << 32 ) | time->LowPart ;
161
+ }
162
+
163
+ typedef struct file_stat {
164
+ int fileType;
165
+ LONG64 lastModified;
166
+ LONG64 size;
167
+ } file_stat_t ;
168
+
169
+ //
170
+ // Retrieves the file attributes for the file specified by |pathStr|.
171
+ // If |followLink| is true, symbolic link targets are resolved.
172
+ //
173
+ // * Returns ERROR_SUCCESS if the file exists and file attributes can be retrieved,
174
+ // * Returns ERROR_SUCCESS with a FILE_TYPE_MISSING if the file does not exist,
175
+ // * Returns a Win32 error code in all other cases.
176
+ //
177
+ DWORD get_file_stat (wchar_t * pathStr, jboolean followLink, file_stat_t * pFileStat) {
178
+ #ifdef WINDOWS_MIN
179
+ WIN32_FILE_ATTRIBUTE_DATA attr;
180
+ BOOL ok = GetFileAttributesExW (pathStr, GetFileExInfoStandard, &attr);
181
+ if (!ok) {
182
+ DWORD error = GetLastError ();
183
+ if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND || error == ERROR_NOT_READY) {
184
+ // Treat device with no media as missing
185
+ pFileStat->lastModified = 0 ;
186
+ pFileStat->size = 0 ;
187
+ pFileStat->fileType = FILE_TYPE_MISSING;
188
+ return ERROR_SUCCESS;
189
+ }
190
+ return error;
191
+ }
192
+ pFileStat->lastModified = lastModifiedNanos (&attr.ftLastWriteTime );
193
+ if (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
194
+ pFileStat->size = 0 ;
195
+ pFileStat->fileType = FILE_TYPE_DIRECTORY;
196
+ } else {
197
+ pFileStat->size = ((LONG64)attr.nFileSizeHigh << 32 ) | attr.nFileSizeLow ;
198
+ pFileStat->fileType = FILE_TYPE_FILE;
199
+ }
200
+ return ERROR_SUCCESS;
201
+ #else // WINDOWS_MIN: Windows Vista+ support for symlinks
202
+ DWORD dwFlagsAndAttributes = FILE_FLAG_BACKUP_SEMANTICS;
203
+ if (!followLink) {
204
+ dwFlagsAndAttributes |= FILE_FLAG_OPEN_REPARSE_POINT;
205
+ }
206
+ HANDLE fileHandle = CreateFileW (
207
+ pathStr, // lpFileName
208
+ GENERIC_READ, // dwDesiredAccess
209
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, // dwShareMode
210
+ NULL , // lpSecurityAttributes
211
+ OPEN_EXISTING, // dwCreationDisposition
212
+ dwFlagsAndAttributes, // dwFlagsAndAttributes
213
+ NULL // hTemplateFile
214
+ );
215
+ if (fileHandle == INVALID_HANDLE_VALUE) {
216
+ DWORD error = GetLastError ();
217
+ if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND || error == ERROR_NOT_READY) {
218
+ // Treat device with no media as missing
219
+ pFileStat->lastModified = 0 ;
220
+ pFileStat->size = 0 ;
221
+ pFileStat->fileType = FILE_TYPE_MISSING;
222
+ return ERROR_SUCCESS;
223
+ }
224
+ return error;
225
+ }
226
+
227
+ // This call allows retrieving almost everything except for the reparseTag
228
+ BY_HANDLE_FILE_INFORMATION fileInfo;
229
+ BOOL ok = GetFileInformationByHandle (fileHandle, &fileInfo);
230
+ if (!ok) {
231
+ DWORD error = GetLastError ();
232
+ CloseHandle (fileHandle);
233
+ return error;
234
+ }
235
+
236
+ // This call allows retrieving the reparse tag
237
+ FILE_ATTRIBUTE_TAG_INFO fileTagInfo;
238
+ ok = GetFileInformationByHandleEx (fileHandle, FileAttributeTagInfo, &fileTagInfo, sizeof (fileTagInfo));
239
+ if (!ok) {
240
+ DWORD error = GetLastError ();
241
+ CloseHandle (fileHandle);
242
+ return error;
243
+ }
244
+
245
+ CloseHandle (fileHandle);
246
+
247
+ pFileStat->lastModified = lastModifiedNanos (&fileInfo.ftLastWriteTime );
248
+ pFileStat->size = 0 ;
249
+ if (is_file_symlink (fileTagInfo.FileAttributes , fileTagInfo.ReparseTag )) {
250
+ pFileStat->fileType = FILE_TYPE_SYMLINK;
251
+ } else if (fileTagInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
252
+ pFileStat->fileType = FILE_TYPE_DIRECTORY;
253
+ } else {
254
+ pFileStat->size = ((LONG64)fileInfo.nFileSizeHigh << 32 ) | fileInfo.nFileSizeLow ;
255
+ pFileStat->fileType = FILE_TYPE_FILE;
256
+ }
257
+ return ERROR_SUCCESS;
258
+ #endif
259
+ }
260
+
130
261
JNIEXPORT void JNICALL
131
262
Java_net_rubygrapefruit_platform_internal_jni_NativeLibraryFunctions_getSystemInfo (JNIEnv *env, jclass target, jobject info, jobject result) {
132
263
jclass infoClass = env->GetObjectClass (info);
@@ -387,44 +518,28 @@ Java_net_rubygrapefruit_platform_internal_jni_FileEventFunctions_closeWatch(JNIE
387
518
free (details);
388
519
}
389
520
390
- jlong lastModifiedNanos (FILETIME* time) {
391
- return ((jlong)time->dwHighDateTime << 32 ) | time->dwLowDateTime ;
392
- }
393
-
394
521
JNIEXPORT void JNICALL
395
- Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_stat (JNIEnv *env, jclass target, jstring path, jobject dest, jobject result) {
522
+ Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_stat (JNIEnv *env, jclass target, jstring path, jboolean followLink, jobject dest, jobject result) {
396
523
jclass destClass = env->GetObjectClass (dest);
397
524
jmethodID mid = env->GetMethodID (destClass, " details" , " (IJJ)V" );
398
525
if (mid == NULL ) {
399
526
mark_failed_with_message (env, " could not find method" , result);
400
527
return ;
401
528
}
402
529
403
- WIN32_FILE_ATTRIBUTE_DATA attr;
404
530
wchar_t * pathStr = java_to_wchar_path (env, path, result);
405
- BOOL ok = GetFileAttributesExW (pathStr, GetFileExInfoStandard, &attr);
531
+ file_stat_t fileStat;
532
+ DWORD errorCode = get_file_stat (pathStr, followLink, &fileStat);
406
533
free (pathStr);
407
- if (!ok) {
408
- DWORD error = GetLastError ();
409
- if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND || error == ERROR_NOT_READY) {
410
- // Treat device with no media as missing
411
- env->CallVoidMethod (dest, mid, (jint)FILE_TYPE_MISSING, (jlong)0 , (jlong)0 );
412
- return ;
413
- }
414
- mark_failed_with_errno (env, " could not file attributes" , result);
534
+ if (errorCode != ERROR_SUCCESS) {
535
+ mark_failed_with_code (env, " could not file attributes" , errorCode, NULL , result);
415
536
return ;
416
537
}
417
- jlong lastModified = lastModifiedNanos (&attr.ftLastWriteTime );
418
- if (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
419
- env->CallVoidMethod (dest, mid, (jint)FILE_TYPE_DIRECTORY, (jlong)0 , lastModified);
420
- } else {
421
- jlong size = ((jlong)attr.nFileSizeHigh << 32 ) | attr.nFileSizeLow ;
422
- env->CallVoidMethod (dest, mid, (jint)FILE_TYPE_FILE, size, lastModified);
423
- }
538
+ env->CallVoidMethod (dest, mid, fileStat.fileType , fileStat.size , fileStat.lastModified );
424
539
}
425
540
426
541
JNIEXPORT void JNICALL
427
- Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_readdir (JNIEnv *env, jclass target, jstring path, jobject contents, jobject result) {
542
+ Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_readdir (JNIEnv *env, jclass target, jstring path, jboolean followLink, jobject contents, jobject result) {
428
543
jclass contentsClass = env->GetObjectClass (contents);
429
544
jmethodID mid = env->GetMethodID (contentsClass, " addFile" , " (Ljava/lang/String;IJJ)V" );
430
545
if (mid == NULL ) {
@@ -434,29 +549,55 @@ Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_readdir(JNIEn
434
549
435
550
WIN32_FIND_DATAW entry;
436
551
wchar_t * pathStr = java_to_wchar_path (env, path, result);
437
- HANDLE dirHandle = FindFirstFileW (pathStr, &entry );
552
+ wchar_t * patternStr = add_suffix (pathStr, wcslen (pathStr), L" \\ * " );
438
553
free (pathStr);
554
+ HANDLE dirHandle = FindFirstFileW (patternStr, &entry);
439
555
if (dirHandle == INVALID_HANDLE_VALUE) {
440
556
mark_failed_with_errno (env, " could not open directory" , result);
557
+ free (patternStr);
441
558
return ;
442
559
}
443
560
444
561
do {
445
562
if (wcscmp (L" ." , entry.cFileName ) == 0 || wcscmp (L" .." , entry.cFileName ) == 0 ) {
446
563
continue ;
447
564
}
565
+
566
+ // If entry is a symbolic link, we may have to get the attributes of the link target
567
+ bool isSymLink = is_file_symlink (entry.dwFileAttributes , entry.dwReserved0 );
568
+ file_stat_t fileInfo;
569
+ if (isSymLink && followLink) {
570
+ // We use patternStr minus the last character ("*") to create the absolute path of the child entry
571
+ wchar_t * childPathStr = add_suffix (patternStr, wcslen (patternStr) - 1 , entry.cFileName );
572
+ DWORD errorCode = get_file_stat (childPathStr, true , &fileInfo);
573
+ free (childPathStr);
574
+ if (errorCode != ERROR_SUCCESS) {
575
+ // If we can't dereference the symbolic link, create a "missing file" entry
576
+ fileInfo.fileType = FILE_TYPE_MISSING;
577
+ fileInfo.size = 0 ;
578
+ fileInfo.lastModified = 0 ;
579
+ }
580
+ } else {
581
+ fileInfo.fileType = isSymLink ?
582
+ FILE_TYPE_SYMLINK :
583
+ (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ?
584
+ FILE_TYPE_DIRECTORY :
585
+ FILE_TYPE_FILE;
586
+ fileInfo.lastModified = lastModifiedNanos (&entry.ftLastWriteTime );
587
+ fileInfo.size = ((jlong)entry.nFileSizeHigh << 32 ) | entry.nFileSizeLow ;
588
+ }
589
+
590
+ // Add entry
448
591
jstring childName = wchar_to_java (env, entry.cFileName , wcslen (entry.cFileName ), result);
449
- jint type = (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? FILE_TYPE_DIRECTORY : FILE_TYPE_FILE;
450
- jlong lastModified = lastModifiedNanos (&entry.ftLastWriteTime );
451
- jlong size = ((jlong)entry.nFileSizeHigh << 32 ) | entry.nFileSizeLow ;
452
- env->CallVoidMethod (contents, mid, childName, type, size, lastModified);
592
+ env->CallVoidMethod (contents, mid, childName, fileInfo.fileType , fileInfo.size , fileInfo.lastModified );
453
593
} while (FindNextFileW (dirHandle, &entry) != 0 );
454
594
455
595
DWORD error = GetLastError ();
456
596
if (error != ERROR_NO_MORE_FILES ) {
457
597
mark_failed_with_errno (env, " could not read next directory entry" , result);
458
598
}
459
599
600
+ free (patternStr);
460
601
FindClose (dirHandle);
461
602
}
462
603
0 commit comments