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