1 /*
2  * Copyright (C) 2008 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 <errno.h>
18 #include <fcntl.h>
19 #include <inttypes.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
24 
25 #include <pagemap/pagemap.h>
26 
27 #include "pm_map.h"
28 
29 static int read_maps(pm_process_t *proc);
30 
31 #define MAX_FILENAME 64
32 
pm_process_create(pm_kernel_t * ker,pid_t pid,pm_process_t ** proc_out)33 int pm_process_create(pm_kernel_t *ker, pid_t pid, pm_process_t **proc_out) {
34     pm_process_t *proc;
35     char filename[MAX_FILENAME];
36     int error;
37 
38     if (!ker || !proc_out)
39         return -1;
40 
41     proc = calloc(1, sizeof(*proc));
42     if (!proc)
43         return errno;
44 
45     proc->ker = ker;
46     proc->pid = pid;
47 
48     error = snprintf(filename, MAX_FILENAME, "/proc/%d/pagemap", pid);
49     if (error < 0 || error >= MAX_FILENAME) {
50         error = (error < 0) ? (errno) : (-1);
51         free(proc);
52         return error;
53     }
54 
55     proc->pagemap_fd = open(filename, O_RDONLY);
56     if (proc->pagemap_fd < 0) {
57         error = errno;
58         free(proc);
59         return error;
60     }
61 
62     error = read_maps(proc);
63     if (error) {
64         free(proc);
65         return error;
66     }
67 
68     *proc_out = proc;
69 
70     return 0;
71 }
72 
pm_process_usage_flags(pm_process_t * proc,pm_memusage_t * usage_out,uint64_t flags_mask,uint64_t required_flags)73 int pm_process_usage_flags(pm_process_t *proc, pm_memusage_t *usage_out,
74                         uint64_t flags_mask, uint64_t required_flags)
75 {
76     pm_memusage_t usage, map_usage;
77     int error;
78     int i;
79 
80     if (!proc || !usage_out)
81         return -1;
82 
83     pm_memusage_zero(&usage);
84 
85     for (i = 0; i < proc->num_maps; i++) {
86         error = pm_map_usage_flags(proc->maps[i], &map_usage, flags_mask,
87                                    required_flags);
88         if (error) return error;
89 
90         pm_memusage_add(&usage, &map_usage);
91     }
92 
93     memcpy(usage_out, &usage, sizeof(pm_memusage_t));
94 
95     return 0;
96 
97 }
98 
pm_process_usage(pm_process_t * proc,pm_memusage_t * usage_out)99 int pm_process_usage(pm_process_t *proc, pm_memusage_t *usage_out) {
100     return pm_process_usage_flags(proc, usage_out, 0, 0);
101 }
102 
pm_process_pagemap_range(pm_process_t * proc,uint64_t low,uint64_t high,uint64_t ** range_out,size_t * len)103 int pm_process_pagemap_range(pm_process_t *proc,
104                              uint64_t low, uint64_t high,
105                              uint64_t **range_out, size_t *len) {
106     uint64_t firstpage;
107     uint64_t numpages;
108     uint64_t *range;
109     off64_t off;
110     int error;
111 
112     if (!proc || (low > high) || !range_out || !len)
113         return -1;
114 
115     if (low == high) {
116         *range_out = NULL;
117         *len = 0;
118         return 0;
119     }
120 
121     firstpage = low / proc->ker->pagesize;
122     numpages = (high - low) / proc->ker->pagesize;
123 
124     range = malloc(numpages * sizeof(uint64_t));
125     if (!range)
126         return errno;
127 
128     off = lseek64(proc->pagemap_fd, firstpage * sizeof(uint64_t), SEEK_SET);
129     if (off == (off_t)-1) {
130         error = errno;
131         free(range);
132         return error;
133     }
134     error = read(proc->pagemap_fd, (char*)range, numpages * sizeof(uint64_t));
135     if (error == 0) {
136         /* EOF, mapping is not in userspace mapping range (probably vectors) */
137         *len = 0;
138         free(range);
139         *range_out = NULL;
140         return 0;
141     } else if (error < 0 || (error > 0 && error < (int)(numpages * sizeof(uint64_t)))) {
142         error = (error < 0) ? errno : -1;
143         free(range);
144         return error;
145     }
146 
147     *range_out = range;
148     *len = numpages;
149 
150     return 0;
151 }
152 
pm_process_maps(pm_process_t * proc,pm_map_t *** maps_out,size_t * len)153 int pm_process_maps(pm_process_t *proc, pm_map_t ***maps_out, size_t *len) {
154     pm_map_t **maps;
155 
156     if (!proc || !maps_out || !len)
157         return -1;
158 
159     if (proc->num_maps) {
160         maps = malloc(proc->num_maps * sizeof(pm_map_t*));
161         if (!maps)
162             return errno;
163 
164         memcpy(maps, proc->maps, proc->num_maps * sizeof(pm_map_t*));
165 
166         *maps_out = maps;
167     } else {
168         *maps_out = NULL;
169     }
170     *len = proc->num_maps;
171 
172     return 0;
173 }
174 
pm_process_workingset(pm_process_t * proc,pm_memusage_t * ws_out,int reset)175 int pm_process_workingset(pm_process_t *proc,
176                           pm_memusage_t *ws_out, int reset) {
177     pm_memusage_t ws, map_ws;
178     char filename[MAX_FILENAME];
179     int fd;
180     int i, j;
181     int error;
182 
183     if (!proc)
184         return -1;
185 
186     if (ws_out) {
187         pm_memusage_zero(&ws);
188         for (i = 0; i < proc->num_maps; i++) {
189             error = pm_map_workingset(proc->maps[i], &map_ws);
190             if (error) return error;
191 
192             pm_memusage_add(&ws, &map_ws);
193         }
194 
195         memcpy(ws_out, &ws, sizeof(ws));
196     }
197 
198     if (reset) {
199         error = snprintf(filename, MAX_FILENAME, "/proc/%d/clear_refs",
200                          proc->pid);
201         if (error < 0 || error >= MAX_FILENAME) {
202             return (error < 0) ? (errno) : (-1);
203         }
204 
205         fd = open(filename, O_WRONLY);
206         if (fd < 0)
207             return errno;
208 
209         write(fd, "1\n", strlen("1\n"));
210 
211         close(fd);
212     }
213 
214     return 0;
215 }
216 
pm_process_destroy(pm_process_t * proc)217 int pm_process_destroy(pm_process_t *proc) {
218     int i;
219 
220     if (!proc)
221         return -1;
222 
223     for (i = 0; i < proc->num_maps; i++) {
224         pm_map_destroy(proc->maps[i]);
225     }
226     free(proc->maps);
227     close(proc->pagemap_fd);
228     free(proc);
229 
230     return 0;
231 }
232 
233 #define INITIAL_MAPS 10
234 #define MAX_LINE 1024
235 #define MAX_PERMS 5
236 
237 /*
238  * #define FOO 123
239  * S(FOO) => "123"
240  */
241 #define _S(n) #n
242 #define S(n) _S(n)
243 
read_maps(pm_process_t * proc)244 static int read_maps(pm_process_t *proc) {
245     char filename[MAX_FILENAME];
246     char line[MAX_LINE], name[MAX_LINE], perms[MAX_PERMS];
247     FILE *maps_f;
248     pm_map_t *map, **maps, **new_maps;
249     int maps_count, maps_size;
250     int error;
251 
252     if (!proc)
253         return -1;
254 
255     maps = calloc(INITIAL_MAPS, sizeof(pm_map_t*));
256     if (!maps)
257         return errno;
258     maps_count = 0; maps_size = INITIAL_MAPS;
259 
260     error = snprintf(filename, MAX_FILENAME, "/proc/%d/maps", proc->pid);
261     if (error < 0 || error >= MAX_FILENAME) {
262         free(maps);
263         return (error < 0) ? (errno) : (-1);
264     }
265 
266     maps_f = fopen(filename, "r");
267     if (!maps_f) {
268         free(maps);
269         return errno;
270     }
271 
272     while (fgets(line, MAX_LINE, maps_f)) {
273         if (maps_count >= maps_size) {
274             new_maps = realloc(maps, 2 * maps_size * sizeof(pm_map_t*));
275             if (!new_maps) {
276                 error = errno;
277                 free(maps);
278                 fclose(maps_f);
279                 return error;
280             }
281             maps = new_maps;
282             maps_size *= 2;
283         }
284 
285         maps[maps_count] = map = calloc(1, sizeof(*map));
286 
287         map->proc = proc;
288 
289         name[0] = '\0';
290         sscanf(line, "%" SCNx64 "-%" SCNx64 " %s %" SCNx64 " %*s %*d %" S(MAX_LINE) "s",
291                &map->start, &map->end, perms, &map->offset, name);
292 
293         map->name = malloc(strlen(name) + 1);
294         if (!map->name) {
295             error = errno;
296             for (; maps_count > 0; maps_count--)
297                 pm_map_destroy(maps[maps_count]);
298             free(maps);
299             fclose(maps_f);
300             return error;
301         }
302         strcpy(map->name, name);
303         if (perms[0] == 'r') map->flags |= PM_MAP_READ;
304         if (perms[1] == 'w') map->flags |= PM_MAP_WRITE;
305         if (perms[2] == 'x') map->flags |= PM_MAP_EXEC;
306 
307         maps_count++;
308     }
309 
310     fclose(maps_f);
311 
312     new_maps = realloc(maps, maps_count * sizeof(pm_map_t*));
313     if (maps_count && !new_maps) {
314         error = errno;
315         free(maps);
316         return error;
317     }
318 
319     proc->maps = new_maps;
320     proc->num_maps = maps_count;
321 
322     return 0;
323 }
324