1 /*
2  * Copyright (c) 1998, 2018, 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 #include <assert.h>
27 #include <sys/types.h>
28 #include <sys/time.h>
29 #include <sys/stat.h>
30 #include <sys/statvfs.h>
31 #include <string.h>
32 #include <stdlib.h>
33 #include <dlfcn.h>
34 #include <limits.h>
35 
36 #include "jni.h"
37 #include "jni_util.h"
38 #include "jlong.h"
39 #include "jvm.h"
40 #include "io_util.h"
41 #include "io_util_md.h"
42 #include "java_io_FileSystem.h"
43 #include "java_io_UnixFileSystem.h"
44 
45 #include <nativehelper/JNIHelp.h>
46 
47 // Android-changed: Fuchsia: Alias *64 on Fuchsia builds. http://b/119496969
48 // #if defined(_ALLBSD_SOURCE)
49 #if defined(_ALLBSD_SOURCE) || defined(__Fuchsia__)
50 #define dirent64 dirent
51 // Android-changed: Integrate OpenJDK 12 commit to use readdir, not readdir_r. b/64362645
52 // #define readdir64_r readdir_r
53 #define readdir64 readdir
54 #define stat64 stat
55 #define statvfs64 statvfs
56 #endif
57 
58 /* -- Field IDs -- */
59 
60 static struct {
61     jfieldID path;
62 } ids;
63 
64 
65 #define NATIVE_METHOD(className, functionName, signature) \
66 { #functionName, signature, (void*)(Java_java_io_ ## className ## _ ## functionName) }
67 
68 JNIEXPORT void JNICALL
Java_java_io_UnixFileSystem_initIDs(JNIEnv * env,jclass cls)69 Java_java_io_UnixFileSystem_initIDs(JNIEnv *env, jclass cls)
70 {
71     jclass fileClass = (*env)->FindClass(env, "java/io/File");
72     if (!fileClass) return;
73     ids.path = (*env)->GetFieldID(env, fileClass,
74                                   "path", "Ljava/lang/String;");
75 }
76 
77 /* -- Path operations -- */
78 
79 // Android-changed: hidden to avoid conflict with libm (b/135018555)
80 __attribute__((visibility("hidden")))
81 extern int canonicalize(char *path, const char *out, int len);
82 
83 JNIEXPORT jstring JNICALL
Java_java_io_UnixFileSystem_canonicalize0(JNIEnv * env,jobject this,jstring pathname)84 Java_java_io_UnixFileSystem_canonicalize0(JNIEnv *env, jobject this,
85                                           jstring pathname)
86 {
87     jstring rv = NULL;
88 
89     WITH_PLATFORM_STRING(env, pathname, path) {
90         char canonicalPath[JVM_MAXPATHLEN];
91         if (canonicalize((char *)path,
92                          canonicalPath, JVM_MAXPATHLEN) < 0) {
93             JNU_ThrowIOExceptionWithLastError(env, "Bad pathname");
94         } else {
95 #ifdef MACOSX
96             rv = newStringPlatform(env, canonicalPath);
97 #else
98             rv = JNU_NewStringPlatform(env, canonicalPath);
99 #endif
100         }
101     } END_PLATFORM_STRING(env, path);
102     return rv;
103 }
104 
105 
106 /* -- Attribute accessors -- */
107 
108 
109 static jboolean
statMode(const char * path,int * mode)110 statMode(const char *path, int *mode)
111 {
112     struct stat64 sb;
113     if (stat64(path, &sb) == 0) {
114         *mode = sb.st_mode;
115         return JNI_TRUE;
116     }
117     return JNI_FALSE;
118 }
119 
120 
121 JNIEXPORT jint JNICALL
Java_java_io_UnixFileSystem_getBooleanAttributes0(JNIEnv * env,jobject this,jstring abspath)122 Java_java_io_UnixFileSystem_getBooleanAttributes0(JNIEnv *env, jobject this,
123                                                   jstring abspath)
124 {
125     jint rv = 0;
126 
127     /* ----- BEGIN android -----
128     WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {*/
129     WITH_PLATFORM_STRING(env, abspath, path) {
130     // ----- END android -----
131         int mode;
132         if (statMode(path, &mode)) {
133             int fmt = mode & S_IFMT;
134             rv = (jint) (java_io_FileSystem_BA_EXISTS
135                   | ((fmt == S_IFREG) ? java_io_FileSystem_BA_REGULAR : 0)
136                   | ((fmt == S_IFDIR) ? java_io_FileSystem_BA_DIRECTORY : 0));
137         }
138     } END_PLATFORM_STRING(env, path);
139     return rv;
140 }
141 
142 // BEGIN Android-removed: Access files through common interface.
143 /*
144 JNIEXPORT jboolean JNICALL
145 Java_java_io_UnixFileSystem_checkAccess(JNIEnv *env, jobject this,
146                                         jobject file, jint a)
147 {
148     jboolean rv = JNI_FALSE;
149     int mode = 0;
150     switch (a) {
151     case java_io_FileSystem_ACCESS_READ:
152         mode = R_OK;
153         break;
154     case java_io_FileSystem_ACCESS_WRITE:
155         mode = W_OK;
156         break;
157     case java_io_FileSystem_ACCESS_EXECUTE:
158         mode = X_OK;
159         break;
160     default: assert(0);
161     }
162     WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
163         if (access(path, mode) == 0) {
164             rv = JNI_TRUE;
165         }
166     } END_PLATFORM_STRING(env, path);
167     return rv;
168 }
169 */
170 // END Android-removed: Access files through common interface.
171 
172 // Android-changed: Name changed because of added thread policy check
173 JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_setPermission0(JNIEnv * env,jobject this,jobject file,jint access,jboolean enable,jboolean owneronly)174 Java_java_io_UnixFileSystem_setPermission0(JNIEnv *env, jobject this,
175                                            jobject file,
176                                            jint access,
177                                            jboolean enable,
178                                            jboolean owneronly)
179 {
180     jboolean rv = JNI_FALSE;
181 
182     WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
183         int amode = 0;
184         int mode;
185         switch (access) {
186         case java_io_FileSystem_ACCESS_READ:
187             if (owneronly)
188                 amode = S_IRUSR;
189             else
190                 amode = S_IRUSR | S_IRGRP | S_IROTH;
191             break;
192         case java_io_FileSystem_ACCESS_WRITE:
193             if (owneronly)
194                 amode = S_IWUSR;
195             else
196                 amode = S_IWUSR | S_IWGRP | S_IWOTH;
197             break;
198         case java_io_FileSystem_ACCESS_EXECUTE:
199             if (owneronly)
200                 amode = S_IXUSR;
201             else
202                 amode = S_IXUSR | S_IXGRP | S_IXOTH;
203             break;
204         default:
205             assert(0);
206         }
207         if (statMode(path, &mode)) {
208             if (enable)
209                 mode |= amode;
210             else
211                 mode &= ~amode;
212             if (chmod(path, mode) >= 0) {
213                 rv = JNI_TRUE;
214             }
215         }
216     } END_PLATFORM_STRING(env, path);
217     return rv;
218 }
219 
220 // Android-changed: Name changed because of added thread policy check
221 JNIEXPORT jlong JNICALL
Java_java_io_UnixFileSystem_getLastModifiedTime0(JNIEnv * env,jobject this,jobject file)222 Java_java_io_UnixFileSystem_getLastModifiedTime0(JNIEnv *env, jobject this,
223                                                  jobject file)
224 {
225     jlong rv = 0;
226 
227     WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
228         struct stat64 sb;
229         if (stat64(path, &sb) == 0) {
230             rv = 1000 * (jlong)sb.st_mtime;
231         }
232     } END_PLATFORM_STRING(env, path);
233     return rv;
234 }
235 
236 // BEGIN Android-removed: Access files through common interface.
237 /*
238 JNIEXPORT jlong JNICALL
239 Java_java_io_UnixFileSystem_getLength(JNIEnv *env, jobject this,
240                                       jobject file)
241 {
242     jlong rv = 0;
243 
244     WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
245         struct stat64 sb;
246         if (stat64(path, &sb) == 0) {
247             rv = sb.st_size;
248         }
249     } END_PLATFORM_STRING(env, path);
250     return rv;
251 }
252 */
253 // END Android-removed: Access files through common interface.
254 
255 
256 /* -- File operations -- */
257 
258 // Android-changed: Name changed because of added thread policy check
259 JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_createFileExclusively0(JNIEnv * env,jclass cls,jstring pathname)260 Java_java_io_UnixFileSystem_createFileExclusively0(JNIEnv *env, jclass cls,
261                                                    jstring pathname)
262 {
263     jboolean rv = JNI_FALSE;
264 
265     WITH_PLATFORM_STRING(env, pathname, path) {
266         FD fd;
267         /* The root directory always exists */
268         if (strcmp (path, "/")) {
269             fd = handleOpen(path, O_RDWR | O_CREAT | O_EXCL, 0666);
270             if (fd < 0) {
271                 if (errno != EEXIST)
272                     JNU_ThrowIOExceptionWithLastError(env, path);
273             } else {
274                 if (close(fd) == -1)
275                     JNU_ThrowIOExceptionWithLastError(env, path);
276                 rv = JNI_TRUE;
277             }
278         }
279     } END_PLATFORM_STRING(env, path);
280     return rv;
281 }
282 
283 
284 // BEGIN Android-removed: Access files through common interface.
285 /*
286 JNIEXPORT jboolean JNICALL
287 Java_java_io_UnixFileSystem_delete0(JNIEnv *env, jobject this,
288                                     jobject file)
289 {
290     jboolean rv = JNI_FALSE;
291 
292     WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
293         if (remove(path) == 0) {
294             rv = JNI_TRUE;
295         }
296     } END_PLATFORM_STRING(env, path);
297     return rv;
298 }
299 */
300 // END Android-removed: Access files through common interface.
301 
302 // Android-changed: Name changed because of added thread policy check
303 JNIEXPORT jobjectArray JNICALL
Java_java_io_UnixFileSystem_list0(JNIEnv * env,jobject this,jobject file)304 Java_java_io_UnixFileSystem_list0(JNIEnv *env, jobject this,
305                                   jobject file)
306 {
307     DIR *dir = NULL;
308     struct dirent64 *ptr;
309     // Android-removed: Integrate OpenJDK 12 commit to use readdir, not readdir_r. b/64362645
310     // struct dirent64 *result;
311     int len, maxlen;
312     jobjectArray rv, old;
313     jclass str_class;
314 
315     str_class = JNU_ClassString(env);
316     CHECK_NULL_RETURN(str_class, NULL);
317 
318 
319     WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
320         dir = opendir(path);
321     } END_PLATFORM_STRING(env, path);
322     if (dir == NULL) return NULL;
323 
324     // Android-removed: Integrate OpenJDK 12 commit to use readdir, not readdir_r. b/64362645
325     /*
326     ptr = malloc(sizeof(struct dirent64) + (PATH_MAX + 1));
327     if (ptr == NULL) {
328         JNU_ThrowOutOfMemoryError(env, "heap allocation failed");
329         closedir(dir);
330         return NULL;
331     }
332     */
333 
334     /* Allocate an initial String array */
335     len = 0;
336     maxlen = 16;
337     rv = (*env)->NewObjectArray(env, maxlen, str_class, NULL);
338     if (rv == NULL) goto error;
339 
340     /* Scan the directory */
341     // Android-changed: Integrate OpenJDK 12 commit to use readdir, not readdir_r. b/64362645
342     // while ((readdir64_r(dir, ptr, &result) == 0)  && (result != NULL)) {
343     while ((ptr = readdir64(dir)) != NULL) {
344         jstring name;
345         if (!strcmp(ptr->d_name, ".") || !strcmp(ptr->d_name, ".."))
346             continue;
347         if (len == maxlen) {
348             old = rv;
349             rv = (*env)->NewObjectArray(env, maxlen <<= 1, str_class, NULL);
350             if (rv == NULL) goto error;
351             if (JNU_CopyObjectArray(env, rv, old, len) < 0) goto error;
352             (*env)->DeleteLocalRef(env, old);
353         }
354 #ifdef MACOSX
355         name = newStringPlatform(env, ptr->d_name);
356 #else
357         name = JNU_NewStringPlatform(env, ptr->d_name);
358 #endif
359         if (name == NULL) goto error;
360         (*env)->SetObjectArrayElement(env, rv, len++, name);
361         (*env)->DeleteLocalRef(env, name);
362     }
363     closedir(dir);
364     // Android-removed: Integrate OpenJDK 12 commit to use readdir, not readdir_r. b/64362645
365     // free(ptr);
366 
367     /* Copy the final results into an appropriately-sized array */
368     old = rv;
369     rv = (*env)->NewObjectArray(env, len, str_class, NULL);
370     if (rv == NULL) {
371         return NULL;
372     }
373     if (JNU_CopyObjectArray(env, rv, old, len) < 0) {
374         return NULL;
375     }
376     return rv;
377 
378  error:
379     closedir(dir);
380     // Android-removed: Integrate OpenJDK 12 commit to use readdir, not readdir_r. b/64362645
381     // free(ptr);
382     return NULL;
383 }
384 
385 // Android-changed: Name changed because of added thread policy check
386 JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_createDirectory0(JNIEnv * env,jobject this,jobject file)387 Java_java_io_UnixFileSystem_createDirectory0(JNIEnv *env, jobject this,
388                                              jobject file)
389 {
390     jboolean rv = JNI_FALSE;
391 
392     WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
393         if (mkdir(path, 0777) == 0) {
394             rv = JNI_TRUE;
395         }
396     } END_PLATFORM_STRING(env, path);
397     return rv;
398 }
399 
400 
401 // BEGIN Android-removed: Access files through common interface.
402 /*
403 JNIEXPORT jboolean JNICALL
404 Java_java_io_UnixFileSystem_rename0(JNIEnv *env, jobject this,
405                                     jobject from, jobject to)
406 {
407     jboolean rv = JNI_FALSE;
408 
409     WITH_FIELD_PLATFORM_STRING(env, from, ids.path, fromPath) {
410         WITH_FIELD_PLATFORM_STRING(env, to, ids.path, toPath) {
411             if (rename(fromPath, toPath) == 0) {
412                 rv = JNI_TRUE;
413             }
414         } END_PLATFORM_STRING(env, toPath);
415     } END_PLATFORM_STRING(env, fromPath);
416     return rv;
417 }
418 */
419 // END Android-removed: Access files through common interface.
420 
421 // Android-changed: Name changed because of added thread policy check
422 JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_setLastModifiedTime0(JNIEnv * env,jobject this,jobject file,jlong time)423 Java_java_io_UnixFileSystem_setLastModifiedTime0(JNIEnv *env, jobject this,
424                                                  jobject file, jlong time)
425 {
426     jboolean rv = JNI_FALSE;
427 
428     WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
429         struct stat64 sb;
430 
431         if (stat64(path, &sb) == 0) {
432             struct timeval tv[2];
433 
434             /* Preserve access time */
435             tv[0].tv_sec = sb.st_atime;
436             tv[0].tv_usec = 0;
437 
438             /* Change last-modified time */
439             tv[1].tv_sec = time / 1000;
440             tv[1].tv_usec = (time % 1000) * 1000;
441 
442             if (utimes(path, tv) == 0)
443                 rv = JNI_TRUE;
444         }
445     } END_PLATFORM_STRING(env, path);
446 
447     return rv;
448 }
449 
450 // Android-changed: Name changed because of added thread policy check
451 JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_setReadOnly0(JNIEnv * env,jobject this,jobject file)452 Java_java_io_UnixFileSystem_setReadOnly0(JNIEnv *env, jobject this,
453                                          jobject file)
454 {
455     jboolean rv = JNI_FALSE;
456 
457     WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
458         int mode;
459         if (statMode(path, &mode)) {
460             if (chmod(path, mode & ~(S_IWUSR | S_IWGRP | S_IWOTH)) >= 0) {
461                 rv = JNI_TRUE;
462             }
463         }
464     } END_PLATFORM_STRING(env, path);
465     return rv;
466 }
467 
468 // Android-changed: Name changed because of added thread policy check
469 JNIEXPORT jlong JNICALL
Java_java_io_UnixFileSystem_getSpace0(JNIEnv * env,jobject this,jobject file,jint t)470 Java_java_io_UnixFileSystem_getSpace0(JNIEnv *env, jobject this,
471                                       jobject file, jint t)
472 {
473     jlong rv = 0L;
474 
475     WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
476         struct statvfs64 fsstat;
477         memset(&fsstat, 0, sizeof(fsstat));
478         if (statvfs64(path, &fsstat) == 0) {
479             switch(t) {
480             case java_io_FileSystem_SPACE_TOTAL:
481                 rv = jlong_mul(long_to_jlong(fsstat.f_frsize),
482                                long_to_jlong(fsstat.f_blocks));
483                 break;
484             case java_io_FileSystem_SPACE_FREE:
485                 rv = jlong_mul(long_to_jlong(fsstat.f_frsize),
486                                long_to_jlong(fsstat.f_bfree));
487                 break;
488             case java_io_FileSystem_SPACE_USABLE:
489                 rv = jlong_mul(long_to_jlong(fsstat.f_frsize),
490                                long_to_jlong(fsstat.f_bavail));
491                 break;
492             default:
493                 assert(0);
494             }
495         }
496     } END_PLATFORM_STRING(env, path);
497     return rv;
498 }
499 
500 static JNINativeMethod gMethods[] = {
501     NATIVE_METHOD(UnixFileSystem, initIDs, "()V"),
502     NATIVE_METHOD(UnixFileSystem, canonicalize0, "(Ljava/lang/String;)Ljava/lang/String;"),
503     NATIVE_METHOD(UnixFileSystem, getBooleanAttributes0, "(Ljava/lang/String;)I"),
504     NATIVE_METHOD(UnixFileSystem, setPermission0, "(Ljava/io/File;IZZ)Z"),
505     NATIVE_METHOD(UnixFileSystem, getLastModifiedTime0, "(Ljava/io/File;)J"),
506     NATIVE_METHOD(UnixFileSystem, createFileExclusively0, "(Ljava/lang/String;)Z"),
507     NATIVE_METHOD(UnixFileSystem, list0, "(Ljava/io/File;)[Ljava/lang/String;"),
508     NATIVE_METHOD(UnixFileSystem, createDirectory0, "(Ljava/io/File;)Z"),
509     NATIVE_METHOD(UnixFileSystem, setLastModifiedTime0, "(Ljava/io/File;J)Z"),
510     NATIVE_METHOD(UnixFileSystem, setReadOnly0, "(Ljava/io/File;)Z"),
511     NATIVE_METHOD(UnixFileSystem, getSpace0, "(Ljava/io/File;I)J"),
512 };
513 
register_java_io_UnixFileSystem(JNIEnv * env)514 void register_java_io_UnixFileSystem(JNIEnv* env) {
515     jniRegisterNativeMethods(env, "java/io/UnixFileSystem", gMethods, NELEM(gMethods));
516 }
517