/*
 * Copyright 2010, The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "FileHandle.h"

#include "DebugHelper.h"

#include <errno.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <string.h>

namespace bcc {

int FileHandle::open(char const *filename, OpenMode::ModeType mode) {
  static int const open_flags[2] = {
    O_RDONLY,
    O_RDWR | O_CREAT | O_TRUNC,
  };

  static int const lock_flags[2] = { LOCK_SH, LOCK_EX };

#if USE_LOGGER
  static char const *const open_mode_str[2] = { "read", "write" };
#endif

  static size_t const RETRY_MAX = 4;

  static useconds_t const RETRY_USEC = 200000UL;

  for (size_t i = 0; i < RETRY_MAX; ++i) {
    // Try to open the file
    mFD = ::open(filename, open_flags[mode], 0644);

    if (mFD < 0) {
      if (errno == EINTR) {
        // Interrupt occurs while opening the file.  Retry.
        continue;
      }

      LOGW("Unable to open %s in %s mode.  (reason: %s)\n",
           filename, open_mode_str[mode], strerror(errno));

      return -1;
    }

    // Try to lock the file
    if (flock(mFD, lock_flags[mode] | LOCK_NB) < 0) {
      LOGW("Unable to acquire the lock immediately, block and wait now ...\n");

      if (flock(mFD, lock_flags[mode]) < 0) {
        LOGE("Unable to acquire the lock. Retry ...\n");

        ::close(mFD);
        mFD = -1;

        usleep(RETRY_USEC);
        continue;
      }
    }

    // Note: From now on, the object is correctly initialized.  We have to
    // use this->close() to close the file now.

    // Check rather we have locked the correct file or not
    struct stat sfd, sfname;

    if (fstat(mFD, &sfd) == -1 || stat(filename, &sfname) == -1 ||
        sfd.st_dev != sfname.st_dev || sfd.st_ino != sfname.st_ino) {
      // The file we locked is different from the given path.  This may
      // occur when someone changes the file node before we lock the file.
      // Just close the file, and retry after sleeping.

      this->close();
      usleep(RETRY_USEC);
      continue;
    }

    // Good, we have open and lock the file correctly.
    LOGV("File opened. fd=%d\n", mFD);
    return mFD;
  }

  LOGW("Unable to open %s in %s mode.\n", filename, open_mode_str[mode]);
  return -1;
}


void FileHandle::close() {
  if (mFD >= 0) {
    flock(mFD, LOCK_UN);
    ::close(mFD);
    LOGV("File closed. fd=%d\n", mFD);
    mFD = -1;
  }
}


ssize_t FileHandle::read(char *buf, size_t count) {
  if (mFD < 0) {
    return -1;
  }

  while (true) {
    ssize_t nread = ::read(mFD, static_cast<void *>(buf), count);

    if (nread >= 0) {
      return nread;
    }

    if (errno != EAGAIN && errno != EINTR) {
      // If the errno is EAGAIN or EINTR, then we try to read again.
      // Otherwise, consider this is a failure.  And returns zero.
      return -1;
    }
  }

  // Unreachable
  return -1;
}


ssize_t FileHandle::write(char const *buf, size_t count) {
  if (mFD < 0) {
    return -1;
  }

  ssize_t written = 0;

  while (count > 0) {
    ssize_t nwrite = ::write(mFD, static_cast<void const *>(buf), count);

    if (nwrite < 0) {
      if (errno != EAGAIN && errno != EINTR) {
        return written;
      }

      continue;
    }

    written += nwrite;
    count -= (size_t)nwrite;
    buf += (size_t)nwrite;
  }

  return written;
}


off_t FileHandle::seek(off_t offset, int whence) {
  return (mFD < 0) ? -1 : lseek(mFD, offset, whence);
}


void FileHandle::truncate() {
  if (mFD >= 0) {
    if (ftruncate(mFD, 0) != 0) {
      LOGE("Unable to truncate the file.\n");
    }
  }
}


} // namespace bcc