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