Codebase list mozc / 7306a9d base / process_mutex.cc
7306a9d

Tree @7306a9d (Download .tar.gz)

process_mutex.cc @7306a9draw · history · blame

// Copyright 2010-2012, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "base/process_mutex.h"

#ifdef OS_WINDOWS
#include <windows.h>
#else
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#endif

#include <map>
#include <string>
#include "base/base.h"
#include "base/singleton.h"
#include "base/util.h"

namespace mozc {

namespace {
string CreateProcessMutexFile(const char *name) {
  name = (name == NULL) ? "NULL" : name;

#ifdef OS_WINDOWS
  string basename;
#else
  string basename = ".";
#endif

  basename += name;
  basename += ".lock";

  return Util::JoinPath(Util::GetUserProfileDirectory(), basename);
}
}  // namespace

bool ProcessMutex::Lock() {
  return LockAndWrite("");
}

#ifdef OS_WINDOWS

ProcessMutex::ProcessMutex(const char *name)
    : handle_(NULL), locked_(false) {
  filename_ = CreateProcessMutexFile(name);
}

ProcessMutex::~ProcessMutex() {
  UnLock();
}

bool ProcessMutex::LockAndWrite(const string &message) {
  wstring wfilename;
  Util::UTF8ToWide(filename_.c_str(), &wfilename);
  handle_ = ::CreateFileW
      (wfilename.c_str(),
       GENERIC_WRITE,
       FILE_SHARE_READ,  // allow read
       NULL,
       CREATE_ALWAYS,
       FILE_ATTRIBUTE_HIDDEN |
       FILE_ATTRIBUTE_SYSTEM |
       FILE_ATTRIBUTE_TEMPORARY |
       FILE_ATTRIBUTE_NOT_CONTENT_INDEXED |
       FILE_FLAG_DELETE_ON_CLOSE,
       NULL);
  locked_ = (INVALID_HANDLE_VALUE != handle_);

  if (!locked_) {
    VLOG(1) << "already locked";
    return locked_;
  }

  if (!message.empty()) {
    DWORD size = 0;
    if (!::WriteFile(handle_, message.data(), message.size(), &size, NULL)) {
      LOG(ERROR) << "Cannot write message: " << ::GetLastError();
    }
  }

  return locked_;
}

bool ProcessMutex::UnLock() {
  if (NULL != handle_) {
    ::CloseHandle(handle_);
    handle_ = NULL;
  }
  Util::Unlink(filename_);
  return true;
}

#else   // OS_WINDOWS

namespace {
// Special workaround for the bad treatment of fcntl.
// Basically, fcntl provides "per-process" file-locking.
// When a process has multiple fds on the same file,
// file lock is released if one fd is closed. This is not
// an expected behavior for Mozc.
//
// Linux man says
// "As well as being removed by an explicit F_UNLCK, record locks are
// automatically released when the process terminates or
// if it closes any file descriptor referring to a file on which
// locks are held.  This is bad: it means that a process can lose
// the locks on a file like /etc/passwd or /etc/mtab when
// for some reason a library function decides to open,
// read and close it."
//
// FileLockerManager a wrapper class providing per-filename
// file locking implemented upon fcntl. Multiple threads on
//  the same process share one fd for file locking.
//
// We will be able to use flock() instead of fcntl since,
// flock() provides "per-fd" file locking. However, we don't
// use it because flock() doesn't work on NFS.
class FileLockManager {
 public:
  bool Lock(const string &filename, int *fd) {
    scoped_lock l(&mutex_);

    if (fd == NULL) {
      LOG(ERROR) << "fd is NULL";
      return false;
    }

    if (filename.empty()) {
      LOG(ERROR) << "filename is empty";
      return false;
    }

    if (fdmap_.find(filename) != fdmap_.end()) {
      VLOG(1) << filename << " is already locked by the same process";
      return false;
    }

    chmod(filename.c_str(), 0600);  // write temporary
    *fd = ::open(filename.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0600);
    if (-1 == *fd) {
      LOG(ERROR) << "open() failed:" << ::strerror(errno);
      return false;
    }

    struct flock command;
    command.l_type = F_WRLCK;
    command.l_whence = SEEK_SET;
    command.l_start = 0;
    command.l_len = 0;

    const int result = ::fcntl(*fd, F_SETLK, &command);
    if (-1 == result) {  // failed
      ::close(*fd);
      LOG(WARNING) << "already locked";
      return false;   // another server is already running
    }

    fdmap_.insert(make_pair(filename, *fd));

    return true;
  }

  void UnLock(const string &filename) {
    scoped_lock l(&mutex_);
    map<string, int>::iterator it = fdmap_.find(filename);
    if (it == fdmap_.end()) {
      LOG(ERROR) << filename << " is not locked";
      return;
    }
    ::close(it->second);
    Util::Unlink(filename);
    fdmap_.erase(it);
  }

  FileLockManager() {}

  ~FileLockManager() {
    for (map<string, int>::const_iterator it = fdmap_.begin();
         it != fdmap_.end(); ++it) {
      ::close(it->second);
    }
    fdmap_.clear();
  }

 private:
  Mutex mutex_;
  map<string, int> fdmap_;
};
}  // namespace

ProcessMutex::ProcessMutex(const char *name) : locked_(false) {
  filename_ = CreateProcessMutexFile(name);
}

ProcessMutex::~ProcessMutex() {
  if (locked_) {
    UnLock();
  }
}

bool ProcessMutex::LockAndWrite(const string &message) {
  int fd = -1;
  if (!Singleton<FileLockManager>::get()->Lock(filename_, &fd)) {
    VLOG(1) << "already locked";
    return false;
  }

  if (fd == -1) {
    LOG(ERROR) << "file descriptor is -1";
    return false;
  }

  if (!message.empty()) {
    if (write(fd, message.data(), message.size()) !=
        message.size()) {
      LOG(ERROR) << "Cannot write message";
      UnLock();
      return false;
    }
  }

  // changed it to read only mode.
  chmod(filename_.c_str(), 0400);
  locked_ = true;
  return true;
}

bool ProcessMutex::UnLock() {
  if (locked_) {
    Singleton<FileLockManager>::get()->UnLock(filename_);
  }
  locked_ = false;
  return true;
}
#endif
}  // namespace mozc