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 "JniException.h"
23#include "ScopedPrimitiveArray.h"
24#include "ScopedUtfChars.h"
25#include "toStringArray.h"
26
27#include <string>
28#include <vector>
29
30#include <dirent.h>
31#include <errno.h>
32#include <fcntl.h>
33#include <stdlib.h>
34#include <string.h>
35#include <sys/stat.h>
36#include <sys/types.h>
37#include <time.h>
38#include <unistd.h>
39#include <utime.h>
40
41static jstring File_canonicalizePath(JNIEnv* env, jclass, jstring javaPath) {
42  ScopedUtfChars path(env, javaPath);
43  if (path.c_str() == NULL) {
44    return NULL;
45  }
46
47  extern bool canonicalize_path(const char* path, std::string& resolved);
48  std::string result;
49  if (!canonicalize_path(path.c_str(), result)) {
50    jniThrowIOException(env, errno);
51    return NULL;
52  }
53  return env->NewStringUTF(result.c_str());
54}
55
56static jboolean File_setLastModifiedImpl(JNIEnv* env, jclass, jstring javaPath, jlong ms) {
57  ScopedUtfChars path(env, javaPath);
58  if (path.c_str() == NULL) {
59    return JNI_FALSE;
60  }
61
62  // We want to preserve the access time.
63  struct stat sb;
64  if (stat(path.c_str(), &sb) == -1) {
65    return JNI_FALSE;
66  }
67
68  // TODO: we could get microsecond resolution with utimes(3), "legacy" though it is.
69  utimbuf times;
70  times.actime = sb.st_atime;
71  times.modtime = static_cast<time_t>(ms / 1000);
72  return (utime(path.c_str(), &times) == 0);
73}
74
75// Iterates over the filenames in the given directory.
76class ScopedReaddir {
77 public:
78  ScopedReaddir(const char* path) {
79    mDirStream = opendir(path);
80    mIsBad = (mDirStream == NULL);
81  }
82
83  ~ScopedReaddir() {
84    if (mDirStream != NULL) {
85      closedir(mDirStream);
86    }
87  }
88
89  // Returns the next filename, or NULL.
90  const char* next() {
91    if (mIsBad) {
92      return NULL;
93    }
94    errno = 0;
95    dirent* result = readdir(mDirStream);
96    if (result != NULL) {
97      return result->d_name;
98    }
99    if (errno != 0) {
100      mIsBad = true;
101    }
102    return NULL;
103  }
104
105  // Has an error occurred on this stream?
106  bool isBad() const {
107    return mIsBad;
108  }
109
110 private:
111  DIR* mDirStream;
112  bool mIsBad;
113
114  // Disallow copy and assignment.
115  ScopedReaddir(const ScopedReaddir&);
116  void operator=(const ScopedReaddir&);
117};
118
119typedef std::vector<std::string> DirEntries;
120
121// Reads the directory referred to by 'pathBytes', adding each directory entry
122// to 'entries'.
123static bool readDirectory(JNIEnv* env, jstring javaPath, DirEntries& entries) {
124  ScopedUtfChars path(env, javaPath);
125  if (path.c_str() == NULL) {
126    return false;
127  }
128
129  ScopedReaddir dir(path.c_str());
130  const char* filename;
131  while ((filename = dir.next()) != NULL) {
132    if (strcmp(filename, ".") != 0 && strcmp(filename, "..") != 0) {
133      // TODO: this hides allocation failures from us. Push directory iteration up into Java?
134      entries.push_back(filename);
135    }
136  }
137  return !dir.isBad();
138}
139
140static jobjectArray File_listImpl(JNIEnv* env, jclass, jstring javaPath) {
141  // Read the directory entries into an intermediate form.
142  DirEntries entries;
143  if (!readDirectory(env, javaPath, entries)) {
144    return NULL;
145  }
146  // Translate the intermediate form into a Java String[].
147  return toStringArray(env, entries);
148}
149
150static JNINativeMethod gMethods[] = {
151  NATIVE_METHOD(File, canonicalizePath, "(Ljava/lang/String;)Ljava/lang/String;"),
152  NATIVE_METHOD(File, listImpl, "(Ljava/lang/String;)[Ljava/lang/String;"),
153  NATIVE_METHOD(File, setLastModifiedImpl, "(Ljava/lang/String;J)Z"),
154};
155void register_java_io_File(JNIEnv* env) {
156  jniRegisterNativeMethods(env, "java/io/File", gMethods, NELEM(gMethods));
157}
158