Bug 876782 - Automounter shouldn't unmount so agressively. r=qdot

This commit is contained in:
Dave Hylands 2013-07-16 13:14:09 -07:00
parent 3d9c4d89ab
commit 527b9ef47f
4 changed files with 297 additions and 8 deletions

View File

@ -28,6 +28,7 @@
#include "nsString.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "OpenFileFinder.h"
#include "Volume.h"
#include "VolumeManager.h"
@ -71,8 +72,9 @@ using namespace mozilla::hal;
#define USE_DEBUG 0
#undef LOG
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "AutoMounter" , ## args)
#define ERR(args...) __android_log_print(ANDROID_LOG_ERROR, "AutoMounter" , ## args)
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "AutoMounter", ## args)
#define LOGW(args...) __android_log_print(ANDROID_LOG_WARN, "AutoMounter", ## args)
#define ERR(args...) __android_log_print(ANDROID_LOG_ERROR, "AutoMounter", ## args)
#if USE_DEBUG
#define DBG(args...) __android_log_print(ANDROID_LOG_DEBUG, "AutoMounter" , ## args)
@ -402,19 +404,50 @@ AutoMounter::UpdateState()
if (vol->IsMountLocked()) {
// The volume is currently locked, so leave it in the mounted
// state.
DBG("UpdateState: Mounted volume %s is locked, leaving",
vol->NameStr());
LOGW("UpdateState: Mounted volume %s is locked, not sharing",
vol->NameStr());
break;
}
// Check to see if there are any open files on the volume and
// don't initiate the unmount while there are open files.
OpenFileFinder::Info fileInfo;
OpenFileFinder fileFinder(vol->MountPoint());
if (fileFinder.First(&fileInfo)) {
LOGW("The following files are open under '%s'",
vol->MountPoint().get());
do {
LOGW(" PID: %d file: '%s' app: '%s' comm: '%s' exe: '%s'\n",
fileInfo.mPid,
fileInfo.mFileName.get(),
fileInfo.mAppName.get(),
fileInfo.mComm.get(),
fileInfo.mExe.get());
} while (fileFinder.Next(&fileInfo));
LOGW("UpdateState: Mounted volume %s has open files, not sharing",
vol->NameStr());
// Check again in 5 seconds to see if the files are closed. Since
// we're trying to share the volume, this implies that we're
// plugged into the PC via USB and this in turn implies that the
// battery is charging, so we don't need to be too concerned about
// wasting battery here.
MessageLoopForIO::current()->
PostDelayedTask(FROM_HERE,
NewRunnableMethod(this, &AutoMounter::UpdateState),
5000);
break;
}
// Volume is mounted, we need to unmount before
// we can share.
DBG("UpdateState: Unmounting %s", vol->NameStr());
LOG("UpdateState: Unmounting %s", vol->NameStr());
vol->StartUnmount(mResponseCallback);
return; // UpdateState will be called again when the Unmount command completes
}
case nsIVolume::STATE_IDLE: {
// Volume is unmounted. We can go ahead and share.
DBG("UpdateState: Sharing %s", vol->NameStr());
LOG("UpdateState: Sharing %s", vol->NameStr());
vol->StartShare(mResponseCallback);
return; // UpdateState will be called again when the Share command completes
}
@ -428,14 +461,14 @@ AutoMounter::UpdateState()
switch (volState) {
case nsIVolume::STATE_SHARED: {
// Volume is shared. We can go ahead and unshare.
DBG("UpdateState: Unsharing %s", vol->NameStr());
LOG("UpdateState: Unsharing %s", vol->NameStr());
vol->StartUnshare(mResponseCallback);
return; // UpdateState will be called again when the Unshare command completes
}
case nsIVolume::STATE_IDLE: {
// Volume is unmounted, try to mount.
DBG("UpdateState: Mounting %s", vol->NameStr());
LOG("UpdateState: Mounting %s", vol->NameStr());
vol->StartMount(mResponseCallback);
return; // UpdateState will be called again when Mount command completes
}

View File

@ -0,0 +1,195 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "OpenFileFinder.h"
#include "mozilla/FileUtils.h"
#include "nsPrintfCString.h"
#include <sys/stat.h>
#include <errno.h>
namespace mozilla {
namespace system {
OpenFileFinder::OpenFileFinder(const nsACString& aPath)
: mPath(aPath),
mProcDir(nullptr),
mFdDir(nullptr),
mPid(0)
{
}
OpenFileFinder::~OpenFileFinder()
{
Close();
}
bool
OpenFileFinder::First(OpenFileFinder::Info* aInfo)
{
Close();
mProcDir = opendir("/proc");
if (!mProcDir) {
return false;
}
mState = NEXT_PID;
return Next(aInfo);
}
bool
OpenFileFinder::Next(OpenFileFinder::Info* aInfo)
{
// NOTE: This function calls readdir and readlink, neither of which should
// block since we're using the proc filesystem, which is a purely
// kernel in-memory filesystem and doesn't depend on external driver
// behaviour.
while (mState != DONE) {
switch (mState) {
case NEXT_PID: {
struct dirent *pidEntry;
pidEntry = readdir(mProcDir);
if (!pidEntry) {
mState = DONE;
break;
}
char *endPtr;
mPid = strtol(pidEntry->d_name, &endPtr, 10);
if (mPid == 0 || *endPtr != '\0') {
// Not a +ve number - ignore
continue;
}
// We've found a /proc/PID directory. Scan open file descriptors.
if (mFdDir) {
closedir(mFdDir);
}
nsPrintfCString fdDirPath("/proc/%d/fd", mPid);
mFdDir = opendir(fdDirPath.get());
if (!mFdDir) {
continue;
}
mState = CHECK_FDS;
}
// Fall through
case CHECK_FDS: {
struct dirent *fdEntry;
while((fdEntry = readdir(mFdDir))) {
if (!strcmp(fdEntry->d_name, ".") ||
!strcmp(fdEntry->d_name, "..")) {
continue;
}
nsPrintfCString fdSymLink("/proc/%d/fd/%s", mPid, fdEntry->d_name);
nsCString resolvedPath;
if (ReadSymLink(fdSymLink, resolvedPath) && PathMatches(resolvedPath)) {
// We found an open file contained within the directory tree passed
// into the constructor.
FillInfo(aInfo, resolvedPath);
return true;
}
}
// We've checked all of the files for this pid, move onto the next one.
mState = NEXT_PID;
continue;
}
case DONE:
default:
mState = DONE; // covers the default case
break;
}
}
return false;
}
void
OpenFileFinder::Close()
{
if (mFdDir) {
closedir(mFdDir);
}
if (mProcDir) {
closedir(mProcDir);
}
}
void
OpenFileFinder::FillInfo(OpenFileFinder::Info* aInfo, const nsACString& aPath)
{
aInfo->mFileName = aPath;
aInfo->mPid = mPid;
nsPrintfCString exePath("/proc/%d/exe", mPid);
ReadSymLink(exePath, aInfo->mExe);
aInfo->mComm.Truncate();
aInfo->mAppName.Truncate();
nsPrintfCString statPath("/proc/%d/stat", mPid);
nsCString statString;
statString.SetLength(200);
char *stat = statString.BeginWriting();
if (!stat) {
return;
}
ReadSysFile(statPath.get(), stat, statString.Length());
// The stat line includes the comm field, surrounded by parenthesis.
// However, the contents of the comm field itself is arbitrary and
// and can include ')', so we search for the rightmost ) as being
// the end of the comm field.
char *closeParen = strrchr(stat, ')');
if (!closeParen) {
return;
}
char *openParen = strchr(stat, '(');
if (!openParen) {
return;
}
if (openParen >= closeParen) {
return;
}
nsDependentCSubstring comm(&openParen[1], closeParen - openParen - 1);
aInfo->mComm = comm;
// There is a single character field after the comm and then
// the parent pid (the field we're interested in).
// ) X ppid
// 01234
int ppid = atoi(&closeParen[4]);
// We assume that we're running in the parent process
if (ppid != getpid()) {
return;
}
// This looks like a content process. The comm field will be the
// app name.
aInfo->mAppName = aInfo->mComm;
}
bool
OpenFileFinder::ReadSymLink(const nsACString& aSymLink, nsACString& aOutPath)
{
aOutPath.Truncate();
const char *symLink = aSymLink.BeginReading();
// Verify that we actually have a symlink.
struct stat st;
if (lstat(symLink, &st)) {
return false;
}
if ((st.st_mode & S_IFMT) != S_IFLNK) {
return false;
}
// Contrary to the documentation st.st_size doesn't seem to be a reliable
// indication of the length when reading from /proc, so we use a fixed
// size buffer instead.
char resolvedSymLink[PATH_MAX];
ssize_t pathLength = readlink(symLink, resolvedSymLink,
sizeof(resolvedSymLink) - 1);
if (pathLength <= 0) {
return false;
}
resolvedSymLink[pathLength] = '\0';
aOutPath.Assign(resolvedSymLink);
return true;
}
} // system
} // mozilla

View File

@ -0,0 +1,60 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_system_openfilefinder_h__
#define mozilla_system_openfilefinder_h__
#include "nsString.h"
#include <dirent.h>
namespace mozilla {
namespace system {
class OpenFileFinder
{
public:
enum State
{
NEXT_PID,
CHECK_FDS,
DONE
};
class Info
{
public:
nsCString mFileName; // name of the the open file
nsCString mAppName; // App which has the file open (if it's a b2g app)
pid_t mPid; // pid of the process which has the file open
nsCString mComm; // comm associated with pid
nsCString mExe; // executable name associated with pid
};
OpenFileFinder(const nsACString& aPath);
~OpenFileFinder();
bool First(Info* aInfo); // Return the first open file
bool Next(Info* aInfo); // Return the next open file
void Close();
private:
void FillInfo(Info *aInfo, const nsACString& aPath);
bool ReadSymLink(const nsACString& aSymLink, nsACString& aOutPath);
bool PathMatches(const nsACString& aPath)
{
return Substring(aPath, 0, mPath.Length()).Equals(mPath);
}
State mState; // Keeps track of what we're doing.
nsCString mPath; // Only report files contained within this directory tree
DIR* mProcDir; // Used for scanning /proc
DIR* mFdDir; // Used for scanning /proc/PID/fd
int mPid; // PID currently being processed
};
} // system
} // mozilla
#endif // mozilla_system_nsvolume_h__

View File

@ -52,6 +52,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
'nsVolumeMountLock.cpp',
'nsVolumeService.cpp',
'nsVolumeStat.cpp',
'OpenFileFinder.cpp',
'TimeZoneSettingObserver.cpp',
'Volume.cpp',
'VolumeCommand.cpp',