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     pm_memusage_pswap_init_handle(&usage, usage_out->p_swap);
85 
86     pm_memusage_zero(&map_usage);
87     pm_memusage_pswap_init_handle(&map_usage, usage_out->p_swap);
88 
89     for (i = 0; i < proc->num_maps; i++) {
90         error = pm_map_usage_flags(proc->maps[i], &map_usage, flags_mask,
91                                    required_flags);
92         if (error) return error;
93 
94         pm_memusage_add(&usage, &map_usage);
95     }
96 
97     memcpy(usage_out, &usage, sizeof(pm_memusage_t));
98 
99     return 0;
100 
101 }
102 
pm_process_usage(pm_process_t * proc,pm_memusage_t * usage_out)103 int pm_process_usage(pm_process_t *proc, pm_memusage_t *usage_out) {
104     return pm_process_usage_flags(proc, usage_out, 0, 0);
105 }
106 
pm_process_pagemap_range(pm_process_t * proc,uint64_t low,uint64_t high,uint64_t ** range_out,size_t * len)107 int pm_process_pagemap_range(pm_process_t *proc,
108                              uint64_t low, uint64_t high,
109                              uint64_t **range_out, size_t *len) {
110     uint64_t firstpage;
111     uint64_t numpages;
112     uint64_t *range;
113     off64_t off;
114     int error;
115 
116     if (!proc || (low > high) || !range_out || !len)
117         return -1;
118 
119     if (low == high) {
120         *range_out = NULL;
121         *len = 0;
122         return 0;
123     }
124 
125     firstpage = low / proc->ker->pagesize;
126     numpages = (high - low) / proc->ker->pagesize;
127 
128     range = malloc(numpages * sizeof(uint64_t));
129     if (!range)
130         return errno;
131 
132     off = lseek64(proc->pagemap_fd, firstpage * sizeof(uint64_t), SEEK_SET);
133     if (off == (off_t)-1) {
134         error = errno;
135         free(range);
136         return error;
137     }
138     error = read(proc->pagemap_fd, (char*)range, numpages * sizeof(uint64_t));
139     if (error == 0) {
140         /* EOF, mapping is not in userspace mapping range (probably vectors) */
141         *len = 0;
142         free(range);
143         *range_out = NULL;
144         return 0;
145     } else if (error < 0 || (error > 0 && error < (int)(numpages * sizeof(uint64_t)))) {
146         error = (error < 0) ? errno : -1;
147         free(range);
148         return error;
149     }
150 
151     *range_out = range;
152     *len = numpages;
153 
154     return 0;
155 }
156 
pm_process_maps(pm_process_t * proc,pm_map_t *** maps_out,size_t * len)157 int pm_process_maps(pm_process_t *proc, pm_map_t ***maps_out, size_t *len) {
158     pm_map_t **maps;
159 
160     if (!proc || !maps_out || !len)
161         return -1;
162 
163     if (proc->num_maps) {
164         maps = malloc(proc->num_maps * sizeof(pm_map_t*));
165         if (!maps)
166             return errno;
167 
168         memcpy(maps, proc->maps, proc->num_maps * sizeof(pm_map_t*));
169 
170         *maps_out = maps;
171     } else {
172         *maps_out = NULL;
173     }
174     *len = proc->num_maps;
175 
176     return 0;
177 }
178 
pm_process_workingset(pm_process_t * proc,pm_memusage_t * ws_out,int reset)179 int pm_process_workingset(pm_process_t *proc,
180                           pm_memusage_t *ws_out, int reset) {
181     pm_memusage_t ws, map_ws;
182     char filename[MAX_FILENAME];
183     int fd;
184     int i, j;
185     int error;
186 
187     if (!proc)
188         return -1;
189 
190     if (ws_out) {
191         pm_memusage_zero(&ws);
192         pm_memusage_pswap_init_handle(&ws, ws_out->p_swap);
193 
194         pm_memusage_zero(&map_ws);
195         pm_memusage_pswap_init_handle(&map_ws, ws_out->p_swap);
196 
197         for (i = 0; i < proc->num_maps; i++) {
198             error = pm_map_workingset(proc->maps[i], &map_ws);
199             if (error) return error;
200 
201             pm_memusage_add(&ws, &map_ws);
202         }
203 
204         memcpy(ws_out, &ws, sizeof(ws));
205     }
206 
207     if (reset) {
208         error = snprintf(filename, MAX_FILENAME, "/proc/%d/clear_refs",
209                          proc->pid);
210         if (error < 0 || error >= MAX_FILENAME) {
211             return (error < 0) ? (errno) : (-1);
212         }
213 
214         fd = open(filename, O_WRONLY);
215         if (fd < 0)
216             return errno;
217 
218         write(fd, "1\n", strlen("1\n"));
219 
220         close(fd);
221     }
222 
223     return 0;
224 }
225 
pm_process_destroy(pm_process_t * proc)226 int pm_process_destroy(pm_process_t *proc) {
227     int i;
228 
229     if (!proc)
230         return -1;
231 
232     for (i = 0; i < proc->num_maps; i++) {
233         pm_map_destroy(proc->maps[i]);
234     }
235     free(proc->maps);
236     close(proc->pagemap_fd);
237     free(proc);
238 
239     return 0;
240 }
241 
242 #define INITIAL_MAPS 10
243 #define MAX_PERMS 5
244 
read_maps(pm_process_t * proc)245 static int read_maps(pm_process_t *proc) {
246     char filename[MAX_FILENAME];
247     char *line = NULL;
248     size_t line_length = 0;
249     char perms[MAX_PERMS];
250     FILE *maps_f;
251     pm_map_t *map, **maps, **new_maps;
252     int maps_count, maps_size;
253     int error;
254 
255     if (!proc)
256         return -1;
257 
258     maps = calloc(INITIAL_MAPS, sizeof(pm_map_t*));
259     if (!maps)
260         return errno;
261     maps_count = 0; maps_size = INITIAL_MAPS;
262 
263     error = snprintf(filename, MAX_FILENAME, "/proc/%d/maps", proc->pid);
264     if (error < 0 || error >= MAX_FILENAME) {
265         free(maps);
266         return (error < 0) ? (errno) : (-1);
267     }
268 
269     maps_f = fopen(filename, "r");
270     if (!maps_f) {
271         free(maps);
272         return errno;
273     }
274 
275     while (getline(&line, &line_length, maps_f) != -1) {
276         line[strlen(line) - 1] = '\0'; // Lose the newline.
277 
278         if (maps_count >= maps_size) {
279             new_maps = realloc(maps, 2 * maps_size * sizeof(pm_map_t*));
280             if (!new_maps) {
281                 error = errno;
282                 free(maps);
283                 free(line);
284                 fclose(maps_f);
285                 return error;
286             }
287             maps = new_maps;
288             maps_size *= 2;
289         }
290 
291         maps[maps_count] = map = calloc(1, sizeof(*map));
292 
293         map->proc = proc;
294 
295         int name_offset;
296         sscanf(line, "%" SCNx64 "-%" SCNx64 " %4s %" SCNx64 " %*s %*d %n",
297                &map->start, &map->end, perms, &map->offset, &name_offset);
298 
299         map->name = strdup(line + name_offset);
300         if (!map->name) {
301             error = errno;
302             for (; maps_count > 0; maps_count--)
303                 pm_map_destroy(maps[maps_count]);
304             free(maps);
305             free(line);
306             fclose(maps_f);
307             return error;
308         }
309 
310         if (perms[0] == 'r') map->flags |= PM_MAP_READ;
311         if (perms[1] == 'w') map->flags |= PM_MAP_WRITE;
312         if (perms[2] == 'x') map->flags |= PM_MAP_EXEC;
313 
314         maps_count++;
315     }
316 
317     free(line);
318     fclose(maps_f);
319 
320     new_maps = realloc(maps, maps_count * sizeof(pm_map_t*));
321     if (maps_count && !new_maps) {
322         error = errno;
323         free(maps);
324         return error;
325     }
326 
327     proc->maps = new_maps;
328     proc->num_maps = maps_count;
329 
330     return 0;
331 }
332