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