1/*
2 * Copyright 2012, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "bcc/Support/FileBase.h"
18
19#include "bcc/Support/Log.h"
20
21#include <sys/file.h>
22#include <sys/stat.h>
23#include <unistd.h>
24
25#include <cerrno>
26#include <cstring>
27#include <new>
28
29#include <utils/FileMap.h>
30
31using namespace bcc;
32
33#ifdef _WIN32
34// TODO: Fix flock usage under windows
35#define LOCK_SH 0
36#define LOCK_EX 0
37#define LOCK_NB 0
38#define LOCK_UN 0
39
40int flock(int fd, int operation) {
41  return 0;
42}
43#endif  // _WIN32
44
45FileBase::FileBase(const std::string &pFilename,
46                   unsigned pOpenFlags,
47                   unsigned pFlags)
48  : mFD(-1),
49    mError(),
50    mName(pFilename), mOpenFlags(pOpenFlags),
51    mShouldUnlock(false),
52    mShouldDelete(false) {
53  // Process pFlags
54#ifdef O_BINARY
55  if (pFlags & kBinary) {
56    mOpenFlags |= O_BINARY;
57  }
58#endif
59  if (pFlags & kTruncate) {
60    mOpenFlags |= O_TRUNC;
61  }
62
63  if (pFlags & kAppend) {
64    mOpenFlags |= O_APPEND;
65  }
66
67  if (pFlags & kDeleteOnClose) {
68    mShouldDelete = true;
69  }
70
71  // Open the file.
72  open();
73
74  return;
75}
76
77FileBase::~FileBase() {
78  close();
79}
80
81bool FileBase::open() {
82  do {
83    // FIXME: Hard-coded permissions (0644) for newly created file should be
84    //        removed and provide a way to let the user configure the value.
85    mFD = ::open(mName.c_str(), mOpenFlags, 0644);
86    if (mFD > 0) {
87      return true;
88    }
89
90    // Some errors occurred ...
91    if (errno != EINTR) {
92      detectError();
93      return false;
94    }
95  } while (true);
96  // unreachable
97}
98
99
100bool FileBase::checkFileIntegrity() {
101  // Check the file integrity by examining whether the inode referring to the mFD
102  // and to the file mName are the same.
103  struct stat fd_stat, file_stat;
104
105  // Get the file status of file descriptor mFD.
106  do {
107    if (::fstat(mFD, &fd_stat) == 0) {
108      break;
109    } else if (errno != EINTR) {
110      detectError();
111      return false;
112    }
113  } while (true);
114
115  // Get the file status of file mName.
116  do {
117    if (::stat(mName.c_str(), &file_stat) == 0) {
118      break;
119    } else if (errno != EINTR) {
120      detectError();
121      return false;
122    }
123  } while (true);
124
125  return ((fd_stat.st_dev == file_stat.st_dev) &&
126          (fd_stat.st_ino == file_stat.st_ino));
127}
128
129void FileBase::detectError() {
130  // Read error from errno.
131  mError.assign(errno, llvm::posix_category());
132}
133
134bool FileBase::lock(enum LockModeEnum pMode,
135                    bool pNonblocking,
136                    unsigned pMaxRetry,
137                    useconds_t pRetryInterval) {
138  int lock_operation;
139  unsigned retry = 0;
140
141  // Check the state.
142  if ((mFD < 0) || hasError()) {
143    return false;
144  }
145
146  // Return immediately if it's already locked.
147  if (mShouldUnlock) {
148    return true;
149  }
150
151  // Determine the lock operation (2nd argument) to the flock().
152  if (pMode == kReadLock) {
153    lock_operation = LOCK_SH;
154  } else if (pMode == kWriteLock) {
155    lock_operation = LOCK_EX;
156  } else {
157    mError.assign(llvm::errc::invalid_argument, llvm::posix_category());
158    return false;
159  }
160
161  if (pNonblocking) {
162    lock_operation |= LOCK_NB;
163  }
164
165  do {
166    if (::flock(mFD, lock_operation) == 0) {
167      mShouldUnlock = true;
168      // Here we got a lock but we need to check whether the mFD still
169      // "represents" the filename (mName) we opened in the contructor. This
170      // check may failed when another process deleted the original file mFD
171      // mapped when we were trying to obtain the lock on the file.
172      if (!checkFileIntegrity()) {
173        if (hasError() || !reopen()) {
174          // Error occurred when check the file integrity or re-open the file.
175          return false;
176        } else {
177          // Wait a while before the next try.
178          ::usleep(pRetryInterval);
179          retry++;
180          continue;
181        }
182      }
183
184      return true;
185    }
186
187    // flock() was not performed successfully. Check the errno to see whether
188    // it's retry-able.
189    if (errno == EINTR) {
190      // flock() was interrupted by delivery of a signal. Restart without
191      // decrement the retry counter.
192      continue;
193    } else if (errno == EWOULDBLOCK) {
194      // The file descriptor was locked by others, wait for a while before next
195      // retry.
196      retry++;
197      ::usleep(pRetryInterval);
198    } else {
199      // There's a fatal error occurs when perform flock(). Return immediately
200      // without further retry.
201      detectError();
202      return false;
203    }
204  } while (retry <= pMaxRetry);
205
206  return false;
207}
208
209void FileBase::unlock() {
210  if (mFD < 0) {
211    return;
212  }
213
214  do {
215    if (::flock(mFD, LOCK_UN) == 0) {
216      mShouldUnlock = false;
217      return;
218    }
219  } while (errno == EINTR);
220
221  detectError();
222  return;
223}
224
225android::FileMap *FileBase::createMap(off_t pOffset, size_t pLength,
226                                      bool pIsReadOnly) {
227  if (mFD < 0 || hasError()) {
228    return NULL;
229  }
230
231  android::FileMap *map = new (std::nothrow) android::FileMap();
232  if (map == NULL) {
233    mError.assign(llvm::errc::not_enough_memory, llvm::system_category());
234    return NULL;
235  }
236
237  if (!map->create(NULL, mFD, pOffset, pLength, pIsReadOnly)) {
238    detectError();
239    map->release();
240    return NULL;
241  }
242
243  return map;
244}
245
246size_t FileBase::getSize() {
247  if (mFD < 0 || hasError()) {
248    return static_cast<size_t>(-1);
249  }
250
251  struct stat file_stat;
252  do {
253    if (::fstat(mFD, &file_stat) == 0) {
254      break;
255    } else if (errno != EINTR) {
256      detectError();
257      return static_cast<size_t>(-1);
258    }
259  } while (true);
260
261  return file_stat.st_size;
262}
263
264off_t FileBase::seek(off_t pOffset) {
265  if ((mFD < 0) || hasError()) {
266    return static_cast<off_t>(-1);
267  }
268
269  do {
270    off_t result = ::lseek(mFD, pOffset, SEEK_SET);
271    if (result == pOffset) {
272      return result;
273    }
274  } while (errno == EINTR);
275
276  detectError();
277  return static_cast<off_t>(-1);
278}
279
280off_t FileBase::tell() {
281  if ((mFD < 0) || hasError()) {
282    return static_cast<off_t>(-1);
283  }
284
285  do {
286    off_t result = ::lseek(mFD, 0, SEEK_CUR);
287    if (result != static_cast<off_t>(-1)) {
288      return result;
289    }
290  } while (errno == EINTR);
291
292  detectError();
293  return static_cast<off_t>(-1);
294}
295
296void FileBase::close() {
297  if (mShouldUnlock) {
298    unlock();
299    mShouldUnlock = false;
300  }
301  if (mFD > 0) {
302    ::close(mFD);
303    mFD = -1;
304  }
305  if (mShouldDelete) {
306    int res = ::remove(mName.c_str());
307    if (res != 0) {
308      ALOGE("Failed to remove file: %s - %s", mName.c_str(), ::strerror(res));
309    }
310  }
311  return;
312}
313