|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 #include "OpenFileFinder.h" |
|
6 |
|
7 #include "mozilla/FileUtils.h" |
|
8 #include "nsPrintfCString.h" |
|
9 |
|
10 #include <sys/stat.h> |
|
11 #include <errno.h> |
|
12 |
|
13 #define USE_DEBUG 0 |
|
14 |
|
15 #undef LOG |
|
16 #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "OpenFileFinder", ## args) |
|
17 #define LOGW(args...) __android_log_print(ANDROID_LOG_WARN, "OpenFileFinder", ## args) |
|
18 #define ERR(args...) __android_log_print(ANDROID_LOG_ERROR, "OpenFileFinder", ## args) |
|
19 |
|
20 #if USE_DEBUG |
|
21 #define DBG(args...) __android_log_print(ANDROID_LOG_DEBUG, "OpenFileFinder" , ## args) |
|
22 #else |
|
23 #define DBG(args...) |
|
24 #endif |
|
25 |
|
26 namespace mozilla { |
|
27 namespace system { |
|
28 |
|
29 OpenFileFinder::OpenFileFinder(const nsACString& aPath, |
|
30 bool aCheckIsB2gOrDescendant /* = true */) |
|
31 : mPath(aPath), |
|
32 mProcDir(nullptr), |
|
33 mFdDir(nullptr), |
|
34 mPid(0), |
|
35 mCheckIsB2gOrDescendant(aCheckIsB2gOrDescendant) |
|
36 { |
|
37 // We assume that we're running in the parent process |
|
38 mMyPid = getpid(); |
|
39 } |
|
40 |
|
41 OpenFileFinder::~OpenFileFinder() |
|
42 { |
|
43 Close(); |
|
44 } |
|
45 |
|
46 bool |
|
47 OpenFileFinder::First(OpenFileFinder::Info* aInfo) |
|
48 { |
|
49 Close(); |
|
50 |
|
51 mProcDir = opendir("/proc"); |
|
52 if (!mProcDir) { |
|
53 return false; |
|
54 } |
|
55 mState = NEXT_PID; |
|
56 return Next(aInfo); |
|
57 } |
|
58 |
|
59 bool |
|
60 OpenFileFinder::Next(OpenFileFinder::Info* aInfo) |
|
61 { |
|
62 // NOTE: This function calls readdir and readlink, neither of which should |
|
63 // block since we're using the proc filesystem, which is a purely |
|
64 // kernel in-memory filesystem and doesn't depend on external driver |
|
65 // behaviour. |
|
66 while (mState != DONE) { |
|
67 switch (mState) { |
|
68 case NEXT_PID: { |
|
69 struct dirent *pidEntry; |
|
70 pidEntry = readdir(mProcDir); |
|
71 if (!pidEntry) { |
|
72 mState = DONE; |
|
73 break; |
|
74 } |
|
75 char *endPtr; |
|
76 mPid = strtol(pidEntry->d_name, &endPtr, 10); |
|
77 if (mPid == 0 || *endPtr != '\0') { |
|
78 // Not a +ve number - ignore |
|
79 continue; |
|
80 } |
|
81 // We've found a /proc/PID directory. Scan open file descriptors. |
|
82 if (mFdDir) { |
|
83 closedir(mFdDir); |
|
84 } |
|
85 nsPrintfCString fdDirPath("/proc/%d/fd", mPid); |
|
86 mFdDir = opendir(fdDirPath.get()); |
|
87 if (!mFdDir) { |
|
88 continue; |
|
89 } |
|
90 mState = CHECK_FDS; |
|
91 } |
|
92 // Fall through |
|
93 case CHECK_FDS: { |
|
94 struct dirent *fdEntry; |
|
95 while((fdEntry = readdir(mFdDir))) { |
|
96 if (!strcmp(fdEntry->d_name, ".") || |
|
97 !strcmp(fdEntry->d_name, "..")) { |
|
98 continue; |
|
99 } |
|
100 nsPrintfCString fdSymLink("/proc/%d/fd/%s", mPid, fdEntry->d_name); |
|
101 nsCString resolvedPath; |
|
102 if (ReadSymLink(fdSymLink, resolvedPath) && PathMatches(resolvedPath)) { |
|
103 // We found an open file contained within the directory tree passed |
|
104 // into the constructor. |
|
105 FillInfo(aInfo, resolvedPath); |
|
106 // If sCheckIsB2gOrDescendant is set false, the caller cares about |
|
107 // all processes which have open files. If sCheckIsB2gOrDescendant |
|
108 // is set false, we only care about the b2g proccess or its descendants. |
|
109 if (!mCheckIsB2gOrDescendant || aInfo->mIsB2gOrDescendant) { |
|
110 return true; |
|
111 } |
|
112 LOG("Ignore process(%d), not a b2g process or its descendant.", |
|
113 aInfo->mPid); |
|
114 } |
|
115 } |
|
116 // We've checked all of the files for this pid, move onto the next one. |
|
117 mState = NEXT_PID; |
|
118 continue; |
|
119 } |
|
120 case DONE: |
|
121 default: |
|
122 mState = DONE; // covers the default case |
|
123 break; |
|
124 } |
|
125 } |
|
126 return false; |
|
127 } |
|
128 |
|
129 void |
|
130 OpenFileFinder::Close() |
|
131 { |
|
132 if (mFdDir) { |
|
133 closedir(mFdDir); |
|
134 } |
|
135 if (mProcDir) { |
|
136 closedir(mProcDir); |
|
137 } |
|
138 } |
|
139 |
|
140 void |
|
141 OpenFileFinder::FillInfo(OpenFileFinder::Info* aInfo, const nsACString& aPath) |
|
142 { |
|
143 aInfo->mFileName = aPath; |
|
144 aInfo->mPid = mPid; |
|
145 nsPrintfCString exePath("/proc/%d/exe", mPid); |
|
146 ReadSymLink(exePath, aInfo->mExe); |
|
147 aInfo->mComm.Truncate(); |
|
148 aInfo->mAppName.Truncate(); |
|
149 nsPrintfCString statPath("/proc/%d/stat", mPid); |
|
150 nsCString statString; |
|
151 statString.SetLength(200); |
|
152 char *stat = statString.BeginWriting(); |
|
153 if (!stat) { |
|
154 return; |
|
155 } |
|
156 ReadSysFile(statPath.get(), stat, statString.Length()); |
|
157 // The stat line includes the comm field, surrounded by parenthesis. |
|
158 // However, the contents of the comm field itself is arbitrary and |
|
159 // and can include ')', so we search for the rightmost ) as being |
|
160 // the end of the comm field. |
|
161 char *closeParen = strrchr(stat, ')'); |
|
162 if (!closeParen) { |
|
163 return; |
|
164 } |
|
165 char *openParen = strchr(stat, '('); |
|
166 if (!openParen) { |
|
167 return; |
|
168 } |
|
169 if (openParen >= closeParen) { |
|
170 return; |
|
171 } |
|
172 nsDependentCSubstring comm(&openParen[1], closeParen - openParen - 1); |
|
173 aInfo->mComm = comm; |
|
174 // There is a single character field after the comm and then |
|
175 // the parent pid (the field we're interested in). |
|
176 // ) X ppid |
|
177 // 01234 |
|
178 int ppid = atoi(&closeParen[4]); |
|
179 |
|
180 if (mPid == mMyPid) { |
|
181 // This is chrome process |
|
182 aInfo->mIsB2gOrDescendant = true; |
|
183 DBG("Chrome process has open file(s)"); |
|
184 return; |
|
185 } |
|
186 // For the rest (non-chrome process), we recursively check the ppid to know |
|
187 // it is a descendant of b2g or not. See bug 931456. |
|
188 while (ppid != mMyPid && ppid != 1) { |
|
189 DBG("Process(%d) is not forked from b2g(%d) or Init(1), keep looking", |
|
190 ppid, mMyPid); |
|
191 nsPrintfCString ppStatPath("/proc/%d/stat", ppid); |
|
192 ReadSysFile(ppStatPath.get(), stat, statString.Length()); |
|
193 closeParen = strrchr(stat, ')'); |
|
194 if (!closeParen) { |
|
195 return; |
|
196 } |
|
197 ppid = atoi(&closeParen[4]); |
|
198 } |
|
199 if (ppid == 1) { |
|
200 // This is a not a b2g process. |
|
201 DBG("Non-b2g process has open file(s)"); |
|
202 aInfo->mIsB2gOrDescendant = false; |
|
203 return; |
|
204 } |
|
205 if (ppid == mMyPid) { |
|
206 // This is a descendant of b2g. |
|
207 DBG("Child process of chrome process has open file(s)"); |
|
208 aInfo->mIsB2gOrDescendant = true; |
|
209 } |
|
210 |
|
211 // This looks like a content process. The comm field will be the |
|
212 // app name. |
|
213 aInfo->mAppName = aInfo->mComm; |
|
214 } |
|
215 |
|
216 bool |
|
217 OpenFileFinder::ReadSymLink(const nsACString& aSymLink, nsACString& aOutPath) |
|
218 { |
|
219 aOutPath.Truncate(); |
|
220 const char *symLink = aSymLink.BeginReading(); |
|
221 |
|
222 // Verify that we actually have a symlink. |
|
223 struct stat st; |
|
224 if (lstat(symLink, &st)) { |
|
225 return false; |
|
226 } |
|
227 if ((st.st_mode & S_IFMT) != S_IFLNK) { |
|
228 return false; |
|
229 } |
|
230 |
|
231 // Contrary to the documentation st.st_size doesn't seem to be a reliable |
|
232 // indication of the length when reading from /proc, so we use a fixed |
|
233 // size buffer instead. |
|
234 |
|
235 char resolvedSymLink[PATH_MAX]; |
|
236 ssize_t pathLength = readlink(symLink, resolvedSymLink, |
|
237 sizeof(resolvedSymLink) - 1); |
|
238 if (pathLength <= 0) { |
|
239 return false; |
|
240 } |
|
241 resolvedSymLink[pathLength] = '\0'; |
|
242 aOutPath.Assign(resolvedSymLink); |
|
243 return true; |
|
244 } |
|
245 |
|
246 } // system |
|
247 } // mozilla |