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    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
103int 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
107int 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
157int 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
179int 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
226int 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
245static 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