1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <dirent.h>
18 #include <inttypes.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <sys/stat.h>
22 #include <sys/types.h>
23 #include <unistd.h>
24 
25 #include <filesystem>
26 #include <fstream>
27 #include <memory>
28 #include <string>
29 #include <vector>
30 
31 #include <android-base/file.h>
32 #include <android-base/logging.h>
33 #include <android-base/parseint.h>
34 #include <android-base/stringprintf.h>
35 #include <procinfo/process_map.h>
36 
37 #include <dmabufinfo/dmabuf_sysfs_stats.h>
38 #include <dmabufinfo/dmabufinfo.h>
39 
40 namespace android {
41 namespace dmabufinfo {
42 
FileIsDmaBuf(const std::string & path)43 static bool FileIsDmaBuf(const std::string& path) {
44     return path.starts_with("/dmabuf");
45 }
46 
47 enum FdInfoResult {
48     OK,
49     NOT_FOUND,
50     ERROR,
51 };
52 
ReadDmaBufFdInfo(pid_t pid,int fd,std::string * name,std::string * exporter,uint64_t * count,uint64_t * size,uint64_t * inode,bool * is_dmabuf_file,const std::string & procfs_path)53 static FdInfoResult ReadDmaBufFdInfo(pid_t pid, int fd, std::string* name, std::string* exporter,
54                              uint64_t* count, uint64_t* size, uint64_t* inode, bool* is_dmabuf_file,
55                              const std::string& procfs_path) {
56     std::string fdinfo =
57             ::android::base::StringPrintf("%s/%d/fdinfo/%d", procfs_path.c_str(), pid, fd);
58     std::ifstream fp(fdinfo);
59     if (!fp) {
60         if (errno == ENOENT) {
61             return NOT_FOUND;
62         }
63         PLOG(ERROR) << "Failed to open " << fdinfo;
64         return ERROR;
65     }
66 
67     for (std::string file_line; getline(fp, file_line);) {
68         const char* line = file_line.c_str();
69         switch (line[0]) {
70             case 'c':
71                 if (strncmp(line, "count:", 6) == 0) {
72                     const char* c = line + 6;
73                     *count = strtoull(c, nullptr, 10);
74                 }
75                 break;
76             case 'e':
77                 if (strncmp(line, "exp_name:", 9) == 0) {
78                     const char* c = line + 9;
79                     *exporter = ::android::base::Trim(c);
80                     *is_dmabuf_file = true;
81                 }
82                 break;
83             case 'n':
84                 if (strncmp(line, "name:", 5) == 0) {
85                     const char* c = line + 5;
86                     *name = ::android::base::Trim(c);
87                 }
88                 break;
89             case 's':
90                 if (strncmp(line, "size:", 5) == 0) {
91                     const char* c = line + 5;
92                     *size = strtoull(c, nullptr, 10);
93                 }
94                 break;
95             case 'i':
96                 if (strncmp(line, "ino:", 4) == 0) {
97                     const char* c = line + 4;
98                     *inode = strtoull(c, nullptr, 10);
99                 }
100                 break;
101         }
102     }
103 
104     return OK;
105 }
106 
107 // Public methods
ReadDmaBufFdRefs(int pid,std::vector<DmaBuffer> * dmabufs,const std::string & procfs_path)108 bool ReadDmaBufFdRefs(int pid, std::vector<DmaBuffer>* dmabufs,
109                              const std::string& procfs_path) {
110     constexpr char permission_err_msg[] =
111             "Failed to read fdinfo - requires either PTRACE_MODE_READ or root depending on "
112             "the device kernel";
113     static bool logged_permission_err = false;
114 
115     std::string fdinfo_dir_path =
116             ::android::base::StringPrintf("%s/%d/fdinfo", procfs_path.c_str(), pid);
117     std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(fdinfo_dir_path.c_str()), &closedir);
118     if (!dir) {
119         // Don't log permission errors to reduce log spam on devices where fdinfo
120         // of other processes can only be read by root.
121         if (errno != EACCES) {
122             PLOG(ERROR) << "Failed to open " << fdinfo_dir_path << " directory";
123         } else if (!logged_permission_err) {
124             LOG(ERROR) << permission_err_msg;
125             logged_permission_err = true;
126         }
127         return false;
128     }
129     struct dirent* dent;
130     while ((dent = readdir(dir.get()))) {
131         int fd;
132         if (!::android::base::ParseInt(dent->d_name, &fd)) {
133             continue;
134         }
135 
136         // Set defaults in case the kernel doesn't give us the information
137         // we need in fdinfo
138         std::string name = "<unknown>";
139         std::string exporter = "<unknown>";
140         uint64_t count = 0;
141         uint64_t size = 0;
142         uint64_t inode = -1;
143         bool is_dmabuf_file = false;
144 
145         auto fdinfo_result = ReadDmaBufFdInfo(pid, fd, &name, &exporter, &count, &size, &inode,
146                                               &is_dmabuf_file, procfs_path);
147         if (fdinfo_result != OK) {
148             if (fdinfo_result == NOT_FOUND) {
149                 continue;
150             }
151             // Don't log permission errors to reduce log spam when the process doesn't
152             // have the PTRACE_MODE_READ permission.
153             if (errno != EACCES) {
154                 LOG(ERROR) << "Failed to read fd info for pid: " << pid << ", fd: " << fd;
155             } else if (!logged_permission_err) {
156                 LOG(ERROR) << permission_err_msg;
157                 logged_permission_err = true;
158             }
159             return false;
160         }
161         if (!is_dmabuf_file) {
162             continue;
163         }
164         if (inode == static_cast<uint64_t>(-1)) {
165             // Fallback to stat() on the fd path to get inode number
166             std::string fd_path =
167                     ::android::base::StringPrintf("%s/%d/fd/%d", procfs_path.c_str(), pid, fd);
168 
169             struct stat sb;
170             if (stat(fd_path.c_str(), &sb) < 0) {
171                 if (errno == ENOENT) {
172                   continue;
173                 }
174                 PLOG(ERROR) << "Failed to stat: " << fd_path;
175                 return false;
176             }
177 
178             inode = sb.st_ino;
179             // If root, calculate size from the allocated blocks.
180             size = sb.st_blocks * 512;
181         }
182 
183         auto buf = std::find_if(dmabufs->begin(), dmabufs->end(),
184                                 [&inode](const DmaBuffer& dbuf) { return dbuf.inode() == inode; });
185         if (buf != dmabufs->end()) {
186             if (buf->name() == "" || buf->name() == "<unknown>") buf->SetName(name);
187             if (buf->exporter() == "" || buf->exporter() == "<unknown>") buf->SetExporter(exporter);
188             if (buf->count() == 0) buf->SetCount(count);
189             buf->AddFdRef(pid);
190             continue;
191         }
192 
193         DmaBuffer& db = dmabufs->emplace_back(inode, size, count, exporter, name);
194         db.AddFdRef(pid);
195     }
196 
197     return true;
198 }
199 
ReadDmaBufMapRefs(pid_t pid,std::vector<DmaBuffer> * dmabufs,const std::string & procfs_path,const std::string & dmabuf_sysfs_path)200 bool ReadDmaBufMapRefs(pid_t pid, std::vector<DmaBuffer>* dmabufs,
201                               const std::string& procfs_path,
202                               const std::string& dmabuf_sysfs_path) {
203     std::string mapspath = ::android::base::StringPrintf("%s/%d/maps", procfs_path.c_str(), pid);
204     std::ifstream fp(mapspath);
205     if (!fp) {
206         LOG(ERROR) << "Failed to open " << mapspath << " for pid: " << pid;
207         return false;
208     }
209 
210     // Process the map if it is dmabuf. Add map reference to existing object in 'dmabufs'
211     // if it was already found. If it wasn't create a new one and append it to 'dmabufs'
212     auto account_dmabuf = [&](const android::procinfo::MapInfo& mapinfo) {
213         // no need to look into this mapping if it is not dmabuf
214         if (!FileIsDmaBuf(mapinfo.name)) {
215             return;
216         }
217 
218         auto buf = std::find_if(
219                 dmabufs->begin(), dmabufs->end(),
220                 [&mapinfo](const DmaBuffer& dbuf) { return dbuf.inode() == mapinfo.inode; });
221         if (buf != dmabufs->end()) {
222             buf->AddMapRef(pid);
223             return;
224         }
225 
226         // We have a new buffer, but unknown count and name and exporter name
227         // Try to lookup exporter name in sysfs
228         std::string exporter;
229         bool sysfs_stats = ReadBufferExporter(mapinfo.inode, &exporter, dmabuf_sysfs_path);
230         if (!sysfs_stats) {
231             exporter = "<unknown>";
232         }
233 
234         // Using the VMA range as the size of the buffer can be misleading,
235         // due to partially mapped buffers or VMAs that extend beyond the
236         // buffer size.
237         //
238         // Attempt to retrieve the real buffer size from sysfs.
239         uint64_t size = 0;
240         if (!sysfs_stats || !ReadBufferSize(mapinfo.inode, &size, dmabuf_sysfs_path)) {
241             size = mapinfo.end - mapinfo.start;
242         }
243 
244         DmaBuffer& dbuf = dmabufs->emplace_back(mapinfo.inode, size, 0, exporter, "<unknown>");
245         dbuf.AddMapRef(pid);
246     };
247 
248     for (std::string line; getline(fp, line);) {
249         if (!::android::procinfo::ReadMapFileContent(line.data(), account_dmabuf)) {
250             LOG(ERROR) << "Failed to parse " << mapspath << " for pid: " << pid;
251             return false;
252         }
253     }
254 
255     return true;
256 }
257 
ReadDmaBufInfo(pid_t pid,std::vector<DmaBuffer> * dmabufs,bool read_fdrefs,const std::string & procfs_path,const std::string & dmabuf_sysfs_path)258 bool ReadDmaBufInfo(pid_t pid, std::vector<DmaBuffer>* dmabufs, bool read_fdrefs,
259                     const std::string& procfs_path, const std::string& dmabuf_sysfs_path) {
260     dmabufs->clear();
261 
262     if (read_fdrefs) {
263         if (!ReadDmaBufFdRefs(pid, dmabufs, procfs_path)) {
264             LOG(ERROR) << "Failed to read dmabuf fd references";
265             return false;
266         }
267     }
268 
269     if (!ReadDmaBufMapRefs(pid, dmabufs, procfs_path, dmabuf_sysfs_path)) {
270         LOG(ERROR) << "Failed to read dmabuf map references";
271         return false;
272     }
273     return true;
274 }
275 
ReadProcfsDmaBufs(std::vector<DmaBuffer> * bufs)276 bool ReadProcfsDmaBufs(std::vector<DmaBuffer>* bufs) {
277     bufs->clear();
278 
279     std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir("/proc"), closedir);
280     if (!dir) {
281         LOG(ERROR) << "Failed to open /proc directory";
282         bufs->clear();
283         return false;
284     }
285 
286     struct dirent* dent;
287     while ((dent = readdir(dir.get()))) {
288         if (dent->d_type != DT_DIR) continue;
289 
290         int pid = atoi(dent->d_name);
291         if (pid == 0) {
292             continue;
293         }
294 
295         if (!ReadDmaBufFdRefs(pid, bufs)) {
296             LOG(ERROR) << "Failed to read dmabuf fd references for pid " << pid;
297         }
298 
299         if (!ReadDmaBufMapRefs(pid, bufs)) {
300             LOG(ERROR) << "Failed to read dmabuf map references for pid " << pid;
301         }
302     }
303 
304     return true;
305 }
306 
307 }  // namespace dmabufinfo
308 }  // namespace android
309