Home | History | Annotate | Download | only in native
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 #define LOG_TAG "File"
     19 
     20 #include "JNIHelp.h"
     21 #include "JniConstants.h"
     22 #include "LocalArray.h"
     23 #include "ScopedFd.h"
     24 #include "ScopedLocalRef.h"
     25 #include "ScopedPrimitiveArray.h"
     26 #include "ScopedUtfChars.h"
     27 
     28 #include <dirent.h>
     29 #include <errno.h>
     30 #include <fcntl.h>
     31 #include <stdlib.h>
     32 #include <string.h>
     33 #include <sys/stat.h>
     34 #include <sys/vfs.h>
     35 #include <sys/types.h>
     36 #include <time.h>
     37 #include <unistd.h>
     38 #include <utime.h>
     39 
     40 static jboolean File_deleteImpl(JNIEnv* env, jclass, jstring javaPath) {
     41     ScopedUtfChars path(env, javaPath);
     42     if (path.c_str() == NULL) {
     43         return JNI_FALSE;
     44     }
     45     return (remove(path.c_str()) == 0);
     46 }
     47 
     48 static bool doStat(JNIEnv* env, jstring javaPath, struct stat& sb) {
     49     ScopedUtfChars path(env, javaPath);
     50     if (path.c_str() == NULL) {
     51         return JNI_FALSE;
     52     }
     53     return (stat(path.c_str(), &sb) == 0);
     54 }
     55 
     56 static jlong File_lengthImpl(JNIEnv* env, jclass, jstring javaPath) {
     57     struct stat sb;
     58     if (!doStat(env, javaPath, sb)) {
     59         // We must return 0 for files that don't exist.
     60         // TODO: shouldn't we throw an IOException for ELOOP or EACCES?
     61         return 0;
     62     }
     63 
     64     /*
     65      * This android-changed code explicitly treats non-regular files (e.g.,
     66      * sockets and block-special devices) as having size zero. Some synthetic
     67      * "regular" files may report an arbitrary non-zero size, but
     68      * in these cases they generally report a block count of zero.
     69      * So, use a zero block count to trump any other concept of
     70      * size.
     71      *
     72      * TODO: why do we do this?
     73      */
     74     if (!S_ISREG(sb.st_mode) || sb.st_blocks == 0) {
     75         return 0;
     76     }
     77     return sb.st_size;
     78 }
     79 
     80 static jlong File_lastModifiedImpl(JNIEnv* env, jclass, jstring javaPath) {
     81     struct stat sb;
     82     if (!doStat(env, javaPath, sb)) {
     83         return 0;
     84     }
     85     return static_cast<jlong>(sb.st_mtime) * 1000L;
     86 }
     87 
     88 static jboolean File_isDirectoryImpl(JNIEnv* env, jclass, jstring javaPath) {
     89     struct stat sb;
     90     return (doStat(env, javaPath, sb) && S_ISDIR(sb.st_mode));
     91 }
     92 
     93 static jboolean File_isFileImpl(JNIEnv* env, jclass, jstring javaPath) {
     94     struct stat sb;
     95     return (doStat(env, javaPath, sb) && S_ISREG(sb.st_mode));
     96 }
     97 
     98 static jboolean doAccess(JNIEnv* env, jstring javaPath, int mode) {
     99     ScopedUtfChars path(env, javaPath);
    100     if (path.c_str() == NULL) {
    101         return JNI_FALSE;
    102     }
    103     return (access(path.c_str(), mode) == 0);
    104 }
    105 
    106 static jboolean File_existsImpl(JNIEnv* env, jclass, jstring javaPath) {
    107     return doAccess(env, javaPath, F_OK);
    108 }
    109 
    110 static jboolean File_canExecuteImpl(JNIEnv* env, jclass, jstring javaPath) {
    111     return doAccess(env, javaPath, X_OK);
    112 }
    113 
    114 static jboolean File_canReadImpl(JNIEnv* env, jclass, jstring javaPath) {
    115     return doAccess(env, javaPath, R_OK);
    116 }
    117 
    118 static jboolean File_canWriteImpl(JNIEnv* env, jclass, jstring javaPath) {
    119     return doAccess(env, javaPath, W_OK);
    120 }
    121 
    122 static jstring File_readlink(JNIEnv* env, jclass, jstring javaPath) {
    123     ScopedUtfChars path(env, javaPath);
    124     if (path.c_str() == NULL) {
    125         return NULL;
    126     }
    127 
    128     // We can't know how big a buffer readlink(2) will need, so we need to
    129     // loop until it says "that fit".
    130     size_t bufSize = 512;
    131     while (true) {
    132         LocalArray<512> buf(bufSize);
    133         ssize_t len = readlink(path.c_str(), &buf[0], buf.size() - 1);
    134         if (len == -1) {
    135             // An error occurred.
    136             return javaPath;
    137         }
    138         if (static_cast<size_t>(len) < buf.size() - 1) {
    139             // The buffer was big enough.
    140             buf[len] = '\0'; // readlink(2) doesn't NUL-terminate.
    141             return env->NewStringUTF(&buf[0]);
    142         }
    143         // Try again with a bigger buffer.
    144         bufSize *= 2;
    145     }
    146 }
    147 
    148 static jboolean File_setLastModifiedImpl(JNIEnv* env, jclass, jstring javaPath, jlong ms) {
    149     ScopedUtfChars path(env, javaPath);
    150     if (path.c_str() == NULL) {
    151         return JNI_FALSE;
    152     }
    153 
    154     // We want to preserve the access time.
    155     struct stat sb;
    156     if (stat(path.c_str(), &sb) == -1) {
    157         return JNI_FALSE;
    158     }
    159 
    160     // TODO: we could get microsecond resolution with utimes(3), "legacy" though it is.
    161     utimbuf times;
    162     times.actime = sb.st_atime;
    163     times.modtime = static_cast<time_t>(ms / 1000);
    164     return (utime(path.c_str(), &times) == 0);
    165 }
    166 
    167 static jboolean doChmod(JNIEnv* env, jstring javaPath, mode_t mask, bool set) {
    168     ScopedUtfChars path(env, javaPath);
    169     if (path.c_str() == NULL) {
    170         return JNI_FALSE;
    171     }
    172 
    173     struct stat sb;
    174     if (stat(path.c_str(), &sb) == -1) {
    175         return JNI_FALSE;
    176     }
    177     mode_t newMode = set ? (sb.st_mode | mask) : (sb.st_mode & ~mask);
    178     return (chmod(path.c_str(), newMode) == 0);
    179 }
    180 
    181 static jboolean File_setExecutableImpl(JNIEnv* env, jclass, jstring javaPath,
    182         jboolean set, jboolean ownerOnly) {
    183     return doChmod(env, javaPath, ownerOnly ? S_IXUSR : (S_IXUSR | S_IXGRP | S_IXOTH), set);
    184 }
    185 
    186 static jboolean File_setReadableImpl(JNIEnv* env, jclass, jstring javaPath,
    187         jboolean set, jboolean ownerOnly) {
    188     return doChmod(env, javaPath, ownerOnly ? S_IRUSR : (S_IRUSR | S_IRGRP | S_IROTH), set);
    189 }
    190 
    191 static jboolean File_setWritableImpl(JNIEnv* env, jclass, jstring javaPath,
    192         jboolean set, jboolean ownerOnly) {
    193     return doChmod(env, javaPath, ownerOnly ? S_IWUSR : (S_IWUSR | S_IWGRP | S_IWOTH), set);
    194 }
    195 
    196 static bool doStatFs(JNIEnv* env, jstring javaPath, struct statfs& sb) {
    197     ScopedUtfChars path(env, javaPath);
    198     if (path.c_str() == NULL) {
    199         return JNI_FALSE;
    200     }
    201 
    202     int rc = statfs(path.c_str(), &sb);
    203     return (rc != -1);
    204 }
    205 
    206 static jlong File_getFreeSpaceImpl(JNIEnv* env, jclass, jstring javaPath) {
    207     struct statfs sb;
    208     if (!doStatFs(env, javaPath, sb)) {
    209         return 0;
    210     }
    211     return sb.f_bfree * sb.f_bsize; // free block count * block size in bytes.
    212 }
    213 
    214 static jlong File_getTotalSpaceImpl(JNIEnv* env, jclass, jstring javaPath) {
    215     struct statfs sb;
    216     if (!doStatFs(env, javaPath, sb)) {
    217         return 0;
    218     }
    219     return sb.f_blocks * sb.f_bsize; // total block count * block size in bytes.
    220 }
    221 
    222 static jlong File_getUsableSpaceImpl(JNIEnv* env, jclass, jstring javaPath) {
    223     struct statfs sb;
    224     if (!doStatFs(env, javaPath, sb)) {
    225         return 0;
    226     }
    227     return sb.f_bavail * sb.f_bsize; // non-root free block count * block size in bytes.
    228 }
    229 
    230 // Iterates over the filenames in the given directory.
    231 class ScopedReaddir {
    232 public:
    233     ScopedReaddir(const char* path) {
    234         mDirStream = opendir(path);
    235         mIsBad = (mDirStream == NULL);
    236     }
    237 
    238     ~ScopedReaddir() {
    239         if (mDirStream != NULL) {
    240             closedir(mDirStream);
    241         }
    242     }
    243 
    244     // Returns the next filename, or NULL.
    245     const char* next() {
    246         dirent* result = NULL;
    247         int rc = readdir_r(mDirStream, &mEntry, &result);
    248         if (rc != 0) {
    249             mIsBad = true;
    250             return NULL;
    251         }
    252         return (result != NULL) ? result->d_name : NULL;
    253     }
    254 
    255     // Has an error occurred on this stream?
    256     bool isBad() const {
    257         return mIsBad;
    258     }
    259 
    260 private:
    261     DIR* mDirStream;
    262     dirent mEntry;
    263     bool mIsBad;
    264 
    265     // Disallow copy and assignment.
    266     ScopedReaddir(const ScopedReaddir&);
    267     void operator=(const ScopedReaddir&);
    268 };
    269 
    270 // DirEntry and DirEntries is a minimal equivalent of std::forward_list
    271 // for the filenames.
    272 struct DirEntry {
    273     DirEntry(const char* filename) : name(strlen(filename)) {
    274         strcpy(&name[0], filename);
    275         next = NULL;
    276     }
    277     // On Linux, the ext family all limit the length of a directory entry to
    278     // less than 256 characters.
    279     LocalArray<256> name;
    280     DirEntry* next;
    281 };
    282 
    283 class DirEntries {
    284 public:
    285     DirEntries() : mSize(0), mHead(NULL) {
    286     }
    287 
    288     ~DirEntries() {
    289         while (mHead) {
    290             pop_front();
    291         }
    292     }
    293 
    294     bool push_front(const char* name) {
    295         DirEntry* oldHead = mHead;
    296         mHead = new DirEntry(name);
    297         if (mHead == NULL) {
    298             return false;
    299         }
    300         mHead->next = oldHead;
    301         ++mSize;
    302         return true;
    303     }
    304 
    305     const char* front() const {
    306         return &mHead->name[0];
    307     }
    308 
    309     void pop_front() {
    310         DirEntry* popped = mHead;
    311         if (popped != NULL) {
    312             mHead = popped->next;
    313             --mSize;
    314             delete popped;
    315         }
    316     }
    317 
    318     size_t size() const {
    319         return mSize;
    320     }
    321 
    322 private:
    323     size_t mSize;
    324     DirEntry* mHead;
    325 
    326     // Disallow copy and assignment.
    327     DirEntries(const DirEntries&);
    328     void operator=(const DirEntries&);
    329 };
    330 
    331 // Reads the directory referred to by 'pathBytes', adding each directory entry
    332 // to 'entries'.
    333 static bool readDirectory(JNIEnv* env, jstring javaPath, DirEntries& entries) {
    334     ScopedUtfChars path(env, javaPath);
    335     if (path.c_str() == NULL) {
    336         return false;
    337     }
    338 
    339     ScopedReaddir dir(path.c_str());
    340     if (dir.isBad()) {
    341         return false;
    342     }
    343     const char* filename;
    344     while ((filename = dir.next()) != NULL) {
    345         if (strcmp(filename, ".") != 0 && strcmp(filename, "..") != 0) {
    346             if (!entries.push_front(filename)) {
    347                 jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
    348                 return false;
    349             }
    350         }
    351     }
    352     return true;
    353 }
    354 
    355 static jobjectArray File_listImpl(JNIEnv* env, jclass, jstring javaPath) {
    356     // Read the directory entries into an intermediate form.
    357     DirEntries files;
    358     if (!readDirectory(env, javaPath, files)) {
    359         return NULL;
    360     }
    361     // Translate the intermediate form into a Java String[].
    362     jobjectArray result = env->NewObjectArray(files.size(), JniConstants::stringClass, NULL);
    363     for (int i = 0; files.size() != 0; files.pop_front(), ++i) {
    364         ScopedLocalRef<jstring> javaFilename(env, env->NewStringUTF(files.front()));
    365         if (env->ExceptionCheck()) {
    366             return NULL;
    367         }
    368         env->SetObjectArrayElement(result, i, javaFilename.get());
    369         if (env->ExceptionCheck()) {
    370             return NULL;
    371         }
    372     }
    373     return result;
    374 }
    375 
    376 static jboolean File_mkdirImpl(JNIEnv* env, jclass, jstring javaPath) {
    377     ScopedUtfChars path(env, javaPath);
    378     if (path.c_str() == NULL) {
    379         return JNI_FALSE;
    380     }
    381 
    382     // On Android, we don't want default permissions to allow global access.
    383     return (mkdir(path.c_str(), S_IRWXU) == 0);
    384 }
    385 
    386 static jboolean File_createNewFileImpl(JNIEnv* env, jclass, jstring javaPath) {
    387     ScopedUtfChars path(env, javaPath);
    388     if (path.c_str() == NULL) {
    389         return JNI_FALSE;
    390     }
    391 
    392     // On Android, we don't want default permissions to allow global access.
    393     ScopedFd fd(open(path.c_str(), O_CREAT | O_EXCL, 0600));
    394     if (fd.get() != -1) {
    395         // We created a new file. Success!
    396         return JNI_TRUE;
    397     }
    398     if (errno == EEXIST) {
    399         // The file already exists.
    400         return JNI_FALSE;
    401     }
    402     jniThrowIOException(env, errno);
    403     return JNI_FALSE; // Ignored by Java; keeps the C++ compiler happy.
    404 }
    405 
    406 static jboolean File_renameToImpl(JNIEnv* env, jclass, jstring javaOldPath, jstring javaNewPath) {
    407     ScopedUtfChars oldPath(env, javaOldPath);
    408     if (oldPath.c_str() == NULL) {
    409         return JNI_FALSE;
    410     }
    411 
    412     ScopedUtfChars newPath(env, javaNewPath);
    413     if (newPath.c_str() == NULL) {
    414         return JNI_FALSE;
    415     }
    416 
    417     return (rename(oldPath.c_str(), newPath.c_str()) == 0);
    418 }
    419 
    420 static JNINativeMethod gMethods[] = {
    421     NATIVE_METHOD(File, canExecuteImpl, "(Ljava/lang/String;)Z"),
    422     NATIVE_METHOD(File, canReadImpl, "(Ljava/lang/String;)Z"),
    423     NATIVE_METHOD(File, canWriteImpl, "(Ljava/lang/String;)Z"),
    424     NATIVE_METHOD(File, createNewFileImpl, "(Ljava/lang/String;)Z"),
    425     NATIVE_METHOD(File, deleteImpl, "(Ljava/lang/String;)Z"),
    426     NATIVE_METHOD(File, existsImpl, "(Ljava/lang/String;)Z"),
    427     NATIVE_METHOD(File, getFreeSpaceImpl, "(Ljava/lang/String;)J"),
    428     NATIVE_METHOD(File, getTotalSpaceImpl, "(Ljava/lang/String;)J"),
    429     NATIVE_METHOD(File, getUsableSpaceImpl, "(Ljava/lang/String;)J"),
    430     NATIVE_METHOD(File, isDirectoryImpl, "(Ljava/lang/String;)Z"),
    431     NATIVE_METHOD(File, isFileImpl, "(Ljava/lang/String;)Z"),
    432     NATIVE_METHOD(File, lastModifiedImpl, "(Ljava/lang/String;)J"),
    433     NATIVE_METHOD(File, lengthImpl, "(Ljava/lang/String;)J"),
    434     NATIVE_METHOD(File, listImpl, "(Ljava/lang/String;)[Ljava/lang/String;"),
    435     NATIVE_METHOD(File, mkdirImpl, "(Ljava/lang/String;)Z"),
    436     NATIVE_METHOD(File, readlink, "(Ljava/lang/String;)Ljava/lang/String;"),
    437     NATIVE_METHOD(File, renameToImpl, "(Ljava/lang/String;Ljava/lang/String;)Z"),
    438     NATIVE_METHOD(File, setExecutableImpl, "(Ljava/lang/String;ZZ)Z"),
    439     NATIVE_METHOD(File, setLastModifiedImpl, "(Ljava/lang/String;J)Z"),
    440     NATIVE_METHOD(File, setReadableImpl, "(Ljava/lang/String;ZZ)Z"),
    441     NATIVE_METHOD(File, setWritableImpl, "(Ljava/lang/String;ZZ)Z"),
    442 };
    443 int register_java_io_File(JNIEnv* env) {
    444     return jniRegisterNativeMethods(env, "java/io/File", gMethods, NELEM(gMethods));
    445 }
    446