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(), ×) == 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