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