]> git.itanic.dy.fi Git - scan-pagemap/blob - parse.c
1170bab12e0ad7d081e69146d6b473a4c38ba91b
[scan-pagemap] / parse.c
1 #include <sys/types.h>
2 #include <dirent.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <errno.h>
7 #include <libgen.h>
8
9 #include "parse.h"
10 #include "pagemap.h"
11
12 static struct maps_list *alloc_maplist(void)
13 {
14         struct maps_list *map;
15
16         map = malloc(sizeof *map);
17         if (map == NULL)
18                 goto err;
19
20         memset(map, 0, sizeof(*map));
21         INIT_LIST_HEAD(&map->list);
22 err:
23         return map;
24 }
25
26 static struct maps *alloc_map(void)
27 {
28         struct maps *map;
29
30         map = malloc(sizeof *map);
31         if (map == NULL)
32                 goto err;
33
34         memset(map, 0, sizeof(*map));
35         INIT_LIST_HEAD(&map->list);
36 err:
37         return map;
38 }
39
40 static struct maps *parse_maps(FILE *file, int pid, int tid)
41 {
42         struct maps *the_map = NULL;
43         char line[1024];
44         int ret;
45
46         while (fgets(line, sizeof(line), file)) {
47                 struct maps *map = alloc_map();
48                 unsigned long start, end;
49                 char name[1024];
50
51                 if (map == NULL)
52                         return 0;
53
54                 if (the_map == NULL)
55                         the_map = map;
56
57                 ret = sscanf(line, "%lx-%lx %*s %*s %*s %*s %s",
58                              &start, &end, name);
59
60                 if (ret < 2) {
61                         printf("Error reading input: %s\n", line);
62                         break;
63                 }
64
65                 map->start = start;
66                 map->end = end;
67                 map->size = end - start;
68                 map->pid = pid;
69                 map->tid = tid;
70
71                 if (ret >= 3)
72                         strncpy(map->name, name, sizeof(map->name));
73
74                 list_add_tail(&map->list, &the_map->list);
75         }
76
77         return the_map;
78 }
79
80 static void clear_pageframe(struct pageframe *pf)
81 {
82         memset(pf, 0, sizeof(*pf));
83 }
84
85 static struct pageframe *alloc_pageframe(void)
86 {
87         struct pageframe *pageframe;
88
89         pageframe = malloc(sizeof *pageframe);
90         if (pageframe == NULL)
91                 goto err;
92
93         clear_pageframe(pageframe);
94         INIT_LIST_HEAD(&pageframe->ml);
95 err:
96         return pageframe;
97 }
98
99 static int compare_pageframe(struct bintree *at, struct bintree *bt)
100 {
101         struct pageframe *a, *b;
102         a = tree_to_pageframe(at);
103         b = tree_to_pageframe(bt);
104
105         return a->pf - b->pf;
106 }
107
108 struct bintree_ops pageframe_ops = {
109         .compare = compare_pageframe,
110 };
111
112 static int read_cmdline(int pid, int tid, char *cmdline, size_t len)
113 {
114         FILE *file;
115         char path[512];
116         int ret;
117
118         snprintf(path, sizeof(path), "/proc/%d/task/%d/cmdline", pid, tid);
119         file = fopen(path, "rb");
120
121         if (!file)
122                 return -1;
123
124         ret = fread(cmdline, 1, len, file);
125         if (ret > 0)
126                 cmdline[ret - 1] = 0;
127         fclose(file);
128
129         return ret > 0 ? 0 : -1;
130 }
131
132 static char *get_name_by_pid(int pid)
133 {
134         static int last_pid;
135         static char cmdline[128];
136         static char *bname;
137
138         if (last_pid == pid)
139                 return bname;
140
141         if (read_cmdline(pid, pid, cmdline, sizeof(cmdline))) {
142                 bname = NULL;
143                 return NULL;
144         }
145
146         bname = basename(cmdline);
147
148         last_pid = pid;
149         return bname;
150 }
151
152 static int should_scan_process(struct parse_opts *opts, struct process *process)
153 {
154         struct pidlist *pid;
155         int match = 0;
156         char *name;
157
158         if (is_parse_option(opts, PARSE_PROCESS_NAME)) {
159                 name = get_name_by_pid(process->pid);
160                 if (!strcmp(opts->name, name ? name : ""))
161                         match = 1;
162         }
163
164         if (is_parse_option(opts, PARSE_PID)) {
165                 list_for_each_entry(pid, &opts->pidlist, list) {
166                         if (pid->pid == process->pid) {
167                                 match = 1;
168                                 break;
169                         }
170                 }
171         }
172
173         if (is_parse_option(opts, PARSE_MAP_NAME))
174                 match = 1;
175
176         if (is_parse_option(opts, PARSE_NOADD_TREE))
177                 match = !match;
178
179         return match;
180 }
181
182 static int should_scan_mapping(struct parse_opts *opts, struct maps *map)
183 {
184         int match = 0;
185
186         if (is_parse_option(opts, PARSE_MAP_NAME)) {
187                 if (!strcmp(opts->name, map->name))
188                         match = 1;
189
190                 if (is_parse_option(opts, PARSE_NOADD_TREE))
191                         match = !match;
192         } else
193                 match = 1;
194
195         return match;
196 }
197
198 static int should_add_to_tree(struct parse_opts *opts, struct pageframe *pf,
199                 struct maps *map)
200 {
201         if (is_parse_option(opts, PARSE_NOADD_TREE))
202                 return 0;
203
204         return 1;
205 }
206
207 /* Read data from the /proc/pid/pagemap file */
208 static int parse_pageframe(FILE *file, struct pageframe *pf_tree,
209                         struct maps *maps, struct parse_opts *opts)
210 {
211         struct maps *map;
212         struct maps_list *tmp;
213         struct pageframe *match, *pageframe = NULL;
214         long start, len, i;
215         unsigned long long pf[10240];
216         int ret, error;
217
218         if (maps == NULL)
219                 return 0;
220
221         /* Go through the list of allocated memory areas */
222         list_for_each_entry(map, &maps->list, list) {
223                 start = map->start >> (PAGE_SHIFT - 3);
224                 len = map->size >> (PAGE_SHIFT);
225
226                 if (!should_scan_mapping(opts, map))
227                         continue;
228
229                 ret = fseek(file, start, SEEK_SET);
230                 if (ret) {
231                         error = errno;
232                         fprintf(stderr, "Error seeking to %lx: %s\n", start,
233                                 strerror(error));
234                         continue;
235                 }
236
237                 for (i = 0; i < len; i++) {
238                         if (!ret) {
239                                 ret = fread(&pf, 1,
240                                         MIN(sizeof(pf), (len - i) * 8), file);
241                         }
242                         if (ret < 0) {
243                                 error = errno;
244                                 continue;
245                         }
246                         if (!pageframe)
247                                 pageframe = alloc_pageframe();
248                         ret -= sizeof(pf[0]);
249
250                         /* ignore unused pages */
251                         if (!pf[ret / sizeof(pf[0])])
252                                 continue;
253
254                         pageframe->pf = (pf[ret / sizeof(pf[0])]);
255
256                         /* ignore unused pages */
257                         if (!(page_swapped(pageframe) ||
258                                         page_present(pageframe)))
259                                 continue;
260
261                         if (should_add_to_tree(opts, pageframe, map)) {
262                                 match = tree_to_pageframe(
263                                         bintree_add(&pf_tree->tree,
264                                                 &pageframe->tree,
265                                                 &pageframe_ops));
266                         } else {
267                                 match = tree_to_pageframe(
268                                         bintree_find(&pf_tree->tree,
269                                                 &pageframe->tree,
270                                                 &pageframe_ops));
271                         }
272
273                         if (match == NULL)
274                                 continue;
275
276                         if (match == pageframe)
277                                 pageframe = NULL;
278
279                         match->refcount++;
280                         /*
281                          * Add a link from the physical page to this
282                          * process's page map
283                          */
284                         tmp = alloc_maplist();
285                         tmp->map = map;
286                         list_add(&tmp->list, &match->ml);
287
288                         if (page_present(match))
289                                 map->pages_present++;
290                         else if (page_swapped(match))
291                                 map->pages_swapped++;
292                 }
293         }
294
295         return 0;
296 }
297
298 static int read_pageframe(int pid, int tid, struct pageframe *pageframe,
299                         struct process *process_list, struct parse_opts *opts)
300 {
301         struct maps *maps;
302         struct process *process;
303         FILE *file;
304         char path[512];
305
306         process = malloc(sizeof(*process));
307         memset(process, 0, sizeof(*process));
308         INIT_LIST_HEAD(&process->list);
309
310         process->pid = pid;
311         process->tid = tid;
312
313         if (!should_scan_process(opts, process))
314                 goto free;
315
316         snprintf(path, sizeof(path), "/proc/%d/task/%d/maps", pid, tid);
317         file = fopen(path, "rb");
318
319         if (!file)
320                 goto free;
321
322         maps = parse_maps(file, pid, tid);
323         fclose(file);
324         process->maps = maps;
325
326         snprintf(path, sizeof(path), "/proc/%d/task/%d/pagemap", pid, tid);
327         file = fopen(path, "rb");
328
329         if (!file)
330                 goto free;
331
332         parse_pageframe(file, pageframe, maps, opts);
333         fclose(file);
334
335         if (read_cmdline(pid, tid, process->name, sizeof(process->name)))
336                 goto free;
337
338         if (maps != NULL) {
339                 list_for_each_entry(maps, &process->maps->list, list) {
340                         process->pages_present += maps->pages_present;
341                         process->pages_swapped += maps->pages_swapped;
342                 }
343         }
344
345         if (!is_parse_option(opts, PARSE_NOADD_TREE))
346                 process->is_initial_pid = 1;
347
348         list_add_tail(&process->list, &process_list->list);
349
350         return 1;
351 free:
352         free(process);
353
354         return 0;
355 }
356
357 static int parse_pid(DIR **dir)
358 {
359         struct dirent *dirent;
360         int error;
361
362 restart:
363         dirent = readdir(*dir);
364         if (!dirent) {
365                 if (errno == 0) {
366                         closedir(*dir);
367                         *dir = NULL;
368                         return 0;
369                 }
370                 error = errno;
371                 printf("Failed to read /proc directory: %s\n", strerror(error));
372                 return -1;
373         }
374
375         if (dirent->d_name[0] < '0' || dirent->d_name[0] > '9')
376                 goto restart;
377
378         return atoi(dirent->d_name);
379 }
380
381 static int opendir_check(DIR **dir, const char *path)
382 {
383         int error;
384
385         if (!*dir) {
386                 *dir = opendir(path);
387                 if (!dir) {
388                         error = errno;
389                         fprintf(stderr, "Failed to open %s directory: %s\n",
390                                 path, strerror(error));
391                         return -1;
392                 }
393         }
394
395         return 0;
396 }
397
398 static int get_next_tid(int pid, DIR **dir)
399 {
400         if (*dir == NULL) {
401                 char path[64];
402
403                 snprintf(path, sizeof(path), "/proc/%d/task/", pid);
404                 if (opendir_check(dir, path))
405                         return -1;
406         }
407
408         return parse_pid(dir);
409 }
410
411 static int get_next_pid(DIR **dir)
412 {
413         if (opendir_check(dir, "/proc"))
414                 return -1;
415
416         return parse_pid(dir);
417 }
418
419 static int get_next_pid_by_name(DIR **dir, char *name)
420 {
421         int pid;
422         char *pname;
423
424         if (opendir_check(dir, "/proc"))
425                 return -1;
426
427         while (1) {
428                 pid = parse_pid(dir);
429                 if (pid <= 0)
430                         break;
431
432                 pname = get_name_by_pid(pid);
433                 if (pname == NULL)
434                         continue;
435                 if (strcmp(pname, name))
436                         continue;
437
438                 return pid;
439         }
440
441         return 0;
442 }
443
444 static int read_pageframe_with_threads(int pid,
445                                 struct pageframe *pageframe,
446                                 struct process *process_list,
447                                 struct parse_opts *opts)
448 {
449         DIR *dir = NULL;
450         int tid;
451         int count = 0;
452
453         while (1) {
454                 if (opts->with_threads)
455                         tid = get_next_tid(pid, &dir);
456                 else
457                         tid = pid;
458
459                 if (tid <= 0)
460                         return count;
461
462                 count += read_pageframe(pid, tid, pageframe, process_list,
463                                         opts);
464
465                 if (!opts->with_threads)
466                         break;
467         }
468
469         return count;
470 }
471
472 int scan_all_pids(struct pageframe *pf, struct process *process_list,
473                 struct parse_opts *opts)
474 {
475         struct pidlist *pidlist;
476         DIR *dir = NULL;
477         int pid;
478         int count = 0;
479
480         if (is_parse_option(opts, PARSE_PROCESS_NAME)) {
481                 while ((pid = get_next_pid_by_name(&dir, opts->name))) {
482                         count += read_pageframe_with_threads(pid, pf,
483                                                         process_list,
484                                                         opts);
485                 }
486                 dir = NULL;
487         }
488
489         if (is_parse_option(opts, PARSE_PID)) {
490                 list_for_each_entry(pidlist, &opts->pidlist, list) {
491                         count += read_pageframe_with_threads(pidlist->pid, pf,
492                                                         process_list, opts);
493                 }
494         }
495
496         if ((count == 0) && !(is_parse_option(opts, PARSE_MAP_NAME))) {
497                 printf("Failed to find any matching processes "
498                         "with given arguments\n");
499                 return -1;
500         }
501
502         if (is_parse_option(opts, PARSE_DUMP))
503                 return 0;
504
505         if (is_parse_option(opts, PARSE_MAP_NAME)) {
506                 while (1) {
507                         pid = get_next_pid(&dir);
508                         if (pid <= 0)
509                                 break;
510                         read_pageframe_with_threads(pid, pf, process_list,
511                                                 opts);
512                 }
513         }
514         /* Do not add new pages in the tree after the initial scan */
515         opts->parse_mask |= PARSE_NOADD_TREE;
516
517         while (1) {
518                 pid = get_next_pid(&dir);
519                 if (pid <= 0)
520                         break;
521                 read_pageframe_with_threads(pid, pf, process_list, opts);
522         }
523
524         return 0;
525 }