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