/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pagemap/pagemap.h>
#include "pm_map.h"
static int read_maps(pm_process_t *proc);
#define MAX_FILENAME 64
int pm_process_create(pm_kernel_t *ker, pid_t pid, pm_process_t **proc_out) {
pm_process_t *proc;
char filename[MAX_FILENAME];
int error;
if (!ker || !proc_out)
return -1;
proc = calloc(1, sizeof(*proc));
if (!proc)
return errno;
proc->ker = ker;
proc->pid = pid;
error = snprintf(filename, MAX_FILENAME, "/proc/%d/pagemap", pid);
if (error < 0 || error >= MAX_FILENAME) {
error = (error < 0) ? (errno) : (-1);
free(proc);
return error;
}
proc->pagemap_fd = open(filename, O_RDONLY);
if (proc->pagemap_fd < 0) {
error = errno;
free(proc);
return error;
}
error = read_maps(proc);
if (error) {
free(proc);
return error;
}
*proc_out = proc;
return 0;
}
int pm_process_usage_flags(pm_process_t *proc, pm_memusage_t *usage_out,
uint64_t flags_mask, uint64_t required_flags)
{
pm_memusage_t usage, map_usage;
int error;
int i;
if (!proc || !usage_out)
return -1;
pm_memusage_zero(&usage);
for (i = 0; i < proc->num_maps; i++) {
error = pm_map_usage_flags(proc->maps[i], &map_usage, flags_mask,
required_flags);
if (error) return error;
pm_memusage_add(&usage, &map_usage);
}
memcpy(usage_out, &usage, sizeof(pm_memusage_t));
return 0;
}
int pm_process_usage(pm_process_t *proc, pm_memusage_t *usage_out) {
return pm_process_usage_flags(proc, usage_out, 0, 0);
}
int pm_process_pagemap_range(pm_process_t *proc,
unsigned long low, unsigned long high,
uint64_t **range_out, size_t *len) {
int firstpage, numpages;
uint64_t *range;
off_t off;
int error;
if (!proc || (low >= high) || !range_out || !len)
return -1;
firstpage = low / proc->ker->pagesize;
numpages = (high - low) / proc->ker->pagesize;
range = malloc(numpages * sizeof(uint64_t));
if (!range)
return errno;
off = lseek(proc->pagemap_fd, firstpage * sizeof(uint64_t), SEEK_SET);
if (off == (off_t)-1) {
error = errno;
free(range);
return error;
}
error = read(proc->pagemap_fd, (char*)range, numpages * sizeof(uint64_t));
if (error == 0) {
/* EOF, mapping is not in userspace mapping range (probably vectors) */
*len = 0;
free(range);
*range_out = NULL;
return 0;
} else if (error < 0 || (error > 0 && error < (int)(numpages * sizeof(uint64_t)))) {
error = (error < 0) ? errno : -1;
free(range);
return error;
}
*range_out = range;
*len = numpages;
return 0;
}
int pm_process_maps(pm_process_t *proc, pm_map_t ***maps_out, size_t *len) {
pm_map_t **maps;
if (!proc || !maps_out || !len)
return -1;
if (proc->num_maps) {
maps = malloc(proc->num_maps * sizeof(pm_map_t*));
if (!maps)
return errno;
memcpy(maps, proc->maps, proc->num_maps * sizeof(pm_map_t*));
*maps_out = maps;
} else {
*maps_out = NULL;
}
*len = proc->num_maps;
return 0;
}
int pm_process_workingset(pm_process_t *proc,
pm_memusage_t *ws_out, int reset) {
pm_memusage_t ws, map_ws;
char filename[MAX_FILENAME];
int fd;
int i, j;
int error;
if (!proc)
return -1;
if (ws_out) {
pm_memusage_zero(&ws);
for (i = 0; i < proc->num_maps; i++) {
error = pm_map_workingset(proc->maps[i], &map_ws);
if (error) return error;
pm_memusage_add(&ws, &map_ws);
}
memcpy(ws_out, &ws, sizeof(ws));
}
if (reset) {
error = snprintf(filename, MAX_FILENAME, "/proc/%d/clear_refs",
proc->pid);
if (error < 0 || error >= MAX_FILENAME) {
return (error < 0) ? (errno) : (-1);
}
fd = open(filename, O_WRONLY);
if (fd < 0)
return errno;
write(fd, "1\n", strlen("1\n"));
close(fd);
}
return 0;
}
int pm_process_destroy(pm_process_t *proc) {
if (!proc)
return -1;
free(proc->maps);
close(proc->pagemap_fd);
free(proc);
return 0;
}
#define INITIAL_MAPS 10
#define MAX_LINE 1024
#define MAX_PERMS 5
/*
* #define FOO 123
* S(FOO) => "123"
*/
#define _S(n) #n
#define S(n) _S(n)
static int read_maps(pm_process_t *proc) {
char filename[MAX_FILENAME];
char line[MAX_LINE], name[MAX_LINE], perms[MAX_PERMS];
FILE *maps_f;
pm_map_t *map, **maps, **new_maps;
int maps_count, maps_size;
int error;
if (!proc)
return -1;
maps = calloc(INITIAL_MAPS, sizeof(pm_map_t*));
if (!maps)
return errno;
maps_count = 0; maps_size = INITIAL_MAPS;
error = snprintf(filename, MAX_FILENAME, "/proc/%d/maps", proc->pid);
if (error < 0 || error >= MAX_FILENAME)
return (error < 0) ? (errno) : (-1);
maps_f = fopen(filename, "r");
if (!maps_f)
return errno;
while (fgets(line, MAX_LINE, maps_f)) {
if (maps_count >= maps_size) {
new_maps = realloc(maps, 2 * maps_size * sizeof(pm_map_t*));
if (!new_maps) {
error = errno;
free(maps);
fclose(maps_f);
return error;
}
maps = new_maps;
maps_size *= 2;
}
maps[maps_count] = map = calloc(1, sizeof(*map));
map->proc = proc;
name[0] = '\0';
sscanf(line, "%lx-%lx %s %lx %*s %*d %" S(MAX_LINE) "s",
&map->start, &map->end, perms, &map->offset, name);
map->name = malloc(strlen(name) + 1);
if (!map->name) {
error = errno;
for (; maps_count > 0; maps_count--)
pm_map_destroy(maps[maps_count]);
free(maps);
return error;
}
strcpy(map->name, name);
if (perms[0] == 'r') map->flags |= PM_MAP_READ;
if (perms[1] == 'w') map->flags |= PM_MAP_WRITE;
if (perms[2] == 'x') map->flags |= PM_MAP_EXEC;
maps_count++;
}
fclose(maps_f);
new_maps = realloc(maps, maps_count * sizeof(pm_map_t*));
if (maps_count && !new_maps) {
error = errno;
free(maps);
return error;
}
proc->maps = new_maps;
proc->num_maps = maps_count;
return 0;
}