• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
File_deleteImpl(JNIEnv * env,jclass,jstring javaPath)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 
doStat(JNIEnv * env,jstring javaPath,struct stat & sb)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 
File_lengthImpl(JNIEnv * env,jclass,jstring javaPath)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 
File_lastModifiedImpl(JNIEnv * env,jclass,jstring javaPath)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 
File_isDirectoryImpl(JNIEnv * env,jclass,jstring javaPath)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 
File_isFileImpl(JNIEnv * env,jclass,jstring javaPath)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 
doAccess(JNIEnv * env,jstring javaPath,int mode)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 
File_existsImpl(JNIEnv * env,jclass,jstring javaPath)106 static jboolean File_existsImpl(JNIEnv* env, jclass, jstring javaPath) {
107     return doAccess(env, javaPath, F_OK);
108 }
109 
File_canExecuteImpl(JNIEnv * env,jclass,jstring javaPath)110 static jboolean File_canExecuteImpl(JNIEnv* env, jclass, jstring javaPath) {
111     return doAccess(env, javaPath, X_OK);
112 }
113 
File_canReadImpl(JNIEnv * env,jclass,jstring javaPath)114 static jboolean File_canReadImpl(JNIEnv* env, jclass, jstring javaPath) {
115     return doAccess(env, javaPath, R_OK);
116 }
117 
File_canWriteImpl(JNIEnv * env,jclass,jstring javaPath)118 static jboolean File_canWriteImpl(JNIEnv* env, jclass, jstring javaPath) {
119     return doAccess(env, javaPath, W_OK);
120 }
121 
File_readlink(JNIEnv * env,jclass,jstring javaPath)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 
File_setLastModifiedImpl(JNIEnv * env,jclass,jstring javaPath,jlong ms)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 
doChmod(JNIEnv * env,jstring javaPath,mode_t mask,bool set)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 
File_setExecutableImpl(JNIEnv * env,jclass,jstring javaPath,jboolean set,jboolean ownerOnly)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 
File_setReadableImpl(JNIEnv * env,jclass,jstring javaPath,jboolean set,jboolean ownerOnly)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 
File_setWritableImpl(JNIEnv * env,jclass,jstring javaPath,jboolean set,jboolean ownerOnly)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 
doStatFs(JNIEnv * env,jstring javaPath,struct statfs & sb)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 
File_getFreeSpaceImpl(JNIEnv * env,jclass,jstring javaPath)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 
File_getTotalSpaceImpl(JNIEnv * env,jclass,jstring javaPath)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 
File_getUsableSpaceImpl(JNIEnv * env,jclass,jstring javaPath)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:
ScopedReaddir(const char * path)233     ScopedReaddir(const char* path) {
234         mDirStream = opendir(path);
235         mIsBad = (mDirStream == NULL);
236     }
237 
~ScopedReaddir()238     ~ScopedReaddir() {
239         if (mDirStream != NULL) {
240             closedir(mDirStream);
241         }
242     }
243 
244     // Returns the next filename, or NULL.
next()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?
isBad() const256     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 {
DirEntryDirEntry273     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:
DirEntries()285     DirEntries() : mSize(0), mHead(NULL) {
286     }
287 
~DirEntries()288     ~DirEntries() {
289         while (mHead) {
290             pop_front();
291         }
292     }
293 
push_front(const char * name)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 
front() const305     const char* front() const {
306         return &mHead->name[0];
307     }
308 
pop_front()309     void pop_front() {
310         DirEntry* popped = mHead;
311         if (popped != NULL) {
312             mHead = popped->next;
313             --mSize;
314             delete popped;
315         }
316     }
317 
size() const318     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'.
readDirectory(JNIEnv * env,jstring javaPath,DirEntries & 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 
File_listImpl(JNIEnv * env,jclass,jstring javaPath)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 
File_mkdirImpl(JNIEnv * env,jclass,jstring javaPath)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 
File_createNewFileImpl(JNIEnv * env,jclass,jstring javaPath)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 
File_renameToImpl(JNIEnv * env,jclass,jstring javaOldPath,jstring javaNewPath)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 };
register_java_io_File(JNIEnv * env)443 int register_java_io_File(JNIEnv* env) {
444     return jniRegisterNativeMethods(env, "java/io/File", gMethods, NELEM(gMethods));
445 }
446