summaryrefslogtreecommitdiffstats
path: root/dom/system/gonk/OpenFileFinder.cpp
blob: 388e813e1799c4e7b3c6c7f605bbb49cbc48c42a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
/* 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>

#undef USE_DEBUG
#define USE_DEBUG 0

#undef LOG
#undef LOGW
#undef ERR
#define LOG(args...)  __android_log_print(ANDROID_LOG_INFO,  "OpenFileFinder", ## args)
#define LOGW(args...) __android_log_print(ANDROID_LOG_WARN,  "OpenFileFinder", ## args)
#define ERR(args...)  __android_log_print(ANDROID_LOG_ERROR, "OpenFileFinder", ## args)

#undef DBG
#if USE_DEBUG
#define DBG(args...)  __android_log_print(ANDROID_LOG_DEBUG, "OpenFileFinder" , ## args)
#else
#define DBG(args...)
#endif

namespace mozilla {
namespace system {

OpenFileFinder::OpenFileFinder(const nsACString& aPath,
                               bool aCheckIsB2gOrDescendant /* = true */)
  : mPath(aPath),
    mProcDir(nullptr),
    mFdDir(nullptr),
    mPid(0),
    mCheckIsB2gOrDescendant(aCheckIsB2gOrDescendant)
{
  // We assume that we're running in the parent process
  mMyPid = getpid();
}

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);
            // If sCheckIsB2gOrDescendant is set false, the caller cares about
            // all processes which have open files. If sCheckIsB2gOrDescendant
            // is set false, we only care about the b2g proccess or its descendants.
            if (!mCheckIsB2gOrDescendant || aInfo->mIsB2gOrDescendant) {
              return true;
            }
            LOG("Ignore process(%d), not a b2g process or its descendant.",
                aInfo->mPid);
          }
        }
        // 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]);

  if (mPid == mMyPid) {
    // This is chrome process
    aInfo->mIsB2gOrDescendant = true;
    DBG("Chrome process has open file(s)");
    return;
  }
  // For the rest (non-chrome process), we recursively check the ppid to know
  // it is a descendant of b2g or not. See bug 931456.
  while (ppid != mMyPid && ppid != 1) {
    DBG("Process(%d) is not forked from b2g(%d) or Init(1), keep looking",
        ppid, mMyPid);
    nsPrintfCString ppStatPath("/proc/%d/stat", ppid);
    ReadSysFile(ppStatPath.get(), stat, statString.Length());
    closeParen = strrchr(stat, ')');
    if (!closeParen) {
      return;
    }
    ppid = atoi(&closeParen[4]);
  }
  if (ppid == 1) {
    // This is a not a b2g process.
    DBG("Non-b2g process has open file(s)");
    aInfo->mIsB2gOrDescendant = false;
    return;
  }
  if (ppid == mMyPid) {
    // This is a descendant of b2g.
    DBG("Child process of chrome process has open file(s)");
    aInfo->mIsB2gOrDescendant = true;
  }

  // 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