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
29static int read_maps(pm_process_t *proc);
30
31#define MAX_FILENAME 64
32
33int 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
73int 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
99int 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
103int 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
153int 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
175int 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
217int 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
244static 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