selftest: move seek_to_smaps_entry() out of mlock2-tests.c
[cascardo/linux.git] / tools / testing / selftests / vm / mlock2-tests.c
1 #define _GNU_SOURCE
2 #include <sys/mman.h>
3 #include <stdint.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <sys/time.h>
7 #include <sys/resource.h>
8 #include <stdbool.h>
9 #include "mlock2.h"
10
11 struct vm_boundaries {
12         unsigned long start;
13         unsigned long end;
14 };
15
16 static int get_vm_area(unsigned long addr, struct vm_boundaries *area)
17 {
18         FILE *file;
19         int ret = 1;
20         char line[1024] = {0};
21         char *end_addr;
22         char *stop;
23         unsigned long start;
24         unsigned long end;
25
26         if (!area)
27                 return ret;
28
29         file = fopen("/proc/self/maps", "r");
30         if (!file) {
31                 perror("fopen");
32                 return ret;
33         }
34
35         memset(area, 0, sizeof(struct vm_boundaries));
36
37         while(fgets(line, 1024, file)) {
38                 end_addr = strchr(line, '-');
39                 if (!end_addr) {
40                         printf("cannot parse /proc/self/maps\n");
41                         goto out;
42                 }
43                 *end_addr = '\0';
44                 end_addr++;
45                 stop = strchr(end_addr, ' ');
46                 if (!stop) {
47                         printf("cannot parse /proc/self/maps\n");
48                         goto out;
49                 }
50                 stop = '\0';
51
52                 sscanf(line, "%lx", &start);
53                 sscanf(end_addr, "%lx", &end);
54
55                 if (start <= addr && end > addr) {
56                         area->start = start;
57                         area->end = end;
58                         ret = 0;
59                         goto out;
60                 }
61         }
62 out:
63         fclose(file);
64         return ret;
65 }
66
67 static uint64_t get_pageflags(unsigned long addr)
68 {
69         FILE *file;
70         uint64_t pfn;
71         unsigned long offset;
72
73         file = fopen("/proc/self/pagemap", "r");
74         if (!file) {
75                 perror("fopen pagemap");
76                 _exit(1);
77         }
78
79         offset = addr / getpagesize() * sizeof(pfn);
80
81         if (fseek(file, offset, SEEK_SET)) {
82                 perror("fseek pagemap");
83                 _exit(1);
84         }
85
86         if (fread(&pfn, sizeof(pfn), 1, file) != 1) {
87                 perror("fread pagemap");
88                 _exit(1);
89         }
90
91         fclose(file);
92         return pfn;
93 }
94
95 static uint64_t get_kpageflags(unsigned long pfn)
96 {
97         uint64_t flags;
98         FILE *file;
99
100         file = fopen("/proc/kpageflags", "r");
101         if (!file) {
102                 perror("fopen kpageflags");
103                 _exit(1);
104         }
105
106         if (fseek(file, pfn * sizeof(flags), SEEK_SET)) {
107                 perror("fseek kpageflags");
108                 _exit(1);
109         }
110
111         if (fread(&flags, sizeof(flags), 1, file) != 1) {
112                 perror("fread kpageflags");
113                 _exit(1);
114         }
115
116         fclose(file);
117         return flags;
118 }
119
120 #define VMFLAGS "VmFlags:"
121
122 static bool is_vmflag_set(unsigned long addr, const char *vmflag)
123 {
124         char *line = NULL;
125         char *flags;
126         size_t size = 0;
127         bool ret = false;
128         FILE *smaps;
129
130         smaps = seek_to_smaps_entry(addr);
131         if (!smaps) {
132                 printf("Unable to parse /proc/self/smaps\n");
133                 goto out;
134         }
135
136         while (getline(&line, &size, smaps) > 0) {
137                 if (!strstr(line, VMFLAGS)) {
138                         free(line);
139                         line = NULL;
140                         size = 0;
141                         continue;
142                 }
143
144                 flags = line + strlen(VMFLAGS);
145                 ret = (strstr(flags, vmflag) != NULL);
146                 goto out;
147         }
148
149 out:
150         free(line);
151         fclose(smaps);
152         return ret;
153 }
154
155 #define SIZE "Size:"
156 #define RSS  "Rss:"
157 #define LOCKED "lo"
158
159 static bool is_vma_lock_on_fault(unsigned long addr)
160 {
161         bool ret = false;
162         bool locked;
163         FILE *smaps = NULL;
164         unsigned long vma_size, vma_rss;
165         char *line = NULL;
166         char *value;
167         size_t size = 0;
168
169         locked = is_vmflag_set(addr, LOCKED);
170         if (!locked)
171                 goto out;
172
173         smaps = seek_to_smaps_entry(addr);
174         if (!smaps) {
175                 printf("Unable to parse /proc/self/smaps\n");
176                 goto out;
177         }
178
179         while (getline(&line, &size, smaps) > 0) {
180                 if (!strstr(line, SIZE)) {
181                         free(line);
182                         line = NULL;
183                         size = 0;
184                         continue;
185                 }
186
187                 value = line + strlen(SIZE);
188                 if (sscanf(value, "%lu kB", &vma_size) < 1) {
189                         printf("Unable to parse smaps entry for Size\n");
190                         goto out;
191                 }
192                 break;
193         }
194
195         while (getline(&line, &size, smaps) > 0) {
196                 if (!strstr(line, RSS)) {
197                         free(line);
198                         line = NULL;
199                         size = 0;
200                         continue;
201                 }
202
203                 value = line + strlen(RSS);
204                 if (sscanf(value, "%lu kB", &vma_rss) < 1) {
205                         printf("Unable to parse smaps entry for Rss\n");
206                         goto out;
207                 }
208                 break;
209         }
210
211         ret = locked && (vma_rss < vma_size);
212 out:
213         free(line);
214         if (smaps)
215                 fclose(smaps);
216         return ret;
217 }
218
219 #define PRESENT_BIT     0x8000000000000000ULL
220 #define PFN_MASK        0x007FFFFFFFFFFFFFULL
221 #define UNEVICTABLE_BIT (1UL << 18)
222
223 static int lock_check(char *map)
224 {
225         unsigned long page_size = getpagesize();
226         uint64_t page1_flags, page2_flags;
227
228         page1_flags = get_pageflags((unsigned long)map);
229         page2_flags = get_pageflags((unsigned long)map + page_size);
230
231         /* Both pages should be present */
232         if (((page1_flags & PRESENT_BIT) == 0) ||
233             ((page2_flags & PRESENT_BIT) == 0)) {
234                 printf("Failed to make both pages present\n");
235                 return 1;
236         }
237
238         page1_flags = get_kpageflags(page1_flags & PFN_MASK);
239         page2_flags = get_kpageflags(page2_flags & PFN_MASK);
240
241         /* Both pages should be unevictable */
242         if (((page1_flags & UNEVICTABLE_BIT) == 0) ||
243             ((page2_flags & UNEVICTABLE_BIT) == 0)) {
244                 printf("Failed to make both pages unevictable\n");
245                 return 1;
246         }
247
248         if (!is_vmflag_set((unsigned long)map, LOCKED)) {
249                 printf("VMA flag %s is missing on page 1\n", LOCKED);
250                 return 1;
251         }
252
253         if (!is_vmflag_set((unsigned long)map + page_size, LOCKED)) {
254                 printf("VMA flag %s is missing on page 2\n", LOCKED);
255                 return 1;
256         }
257
258         return 0;
259 }
260
261 static int unlock_lock_check(char *map)
262 {
263         unsigned long page_size = getpagesize();
264         uint64_t page1_flags, page2_flags;
265
266         page1_flags = get_pageflags((unsigned long)map);
267         page2_flags = get_pageflags((unsigned long)map + page_size);
268         page1_flags = get_kpageflags(page1_flags & PFN_MASK);
269         page2_flags = get_kpageflags(page2_flags & PFN_MASK);
270
271         if ((page1_flags & UNEVICTABLE_BIT) || (page2_flags & UNEVICTABLE_BIT)) {
272                 printf("A page is still marked unevictable after unlock\n");
273                 return 1;
274         }
275
276         if (is_vmflag_set((unsigned long)map, LOCKED)) {
277                 printf("VMA flag %s is present on page 1 after unlock\n", LOCKED);
278                 return 1;
279         }
280
281         if (is_vmflag_set((unsigned long)map + page_size, LOCKED)) {
282                 printf("VMA flag %s is present on page 2 after unlock\n", LOCKED);
283                 return 1;
284         }
285
286         return 0;
287 }
288
289 static int test_mlock_lock()
290 {
291         char *map;
292         int ret = 1;
293         unsigned long page_size = getpagesize();
294
295         map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
296                    MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
297         if (map == MAP_FAILED) {
298                 perror("test_mlock_locked mmap");
299                 goto out;
300         }
301
302         if (mlock2_(map, 2 * page_size, 0)) {
303                 if (errno == ENOSYS) {
304                         printf("Cannot call new mlock family, skipping test\n");
305                         _exit(0);
306                 }
307                 perror("mlock2(0)");
308                 goto unmap;
309         }
310
311         if (lock_check(map))
312                 goto unmap;
313
314         /* Now unlock and recheck attributes */
315         if (munlock(map, 2 * page_size)) {
316                 perror("munlock()");
317                 goto unmap;
318         }
319
320         ret = unlock_lock_check(map);
321
322 unmap:
323         munmap(map, 2 * page_size);
324 out:
325         return ret;
326 }
327
328 static int onfault_check(char *map)
329 {
330         unsigned long page_size = getpagesize();
331         uint64_t page1_flags, page2_flags;
332
333         page1_flags = get_pageflags((unsigned long)map);
334         page2_flags = get_pageflags((unsigned long)map + page_size);
335
336         /* Neither page should be present */
337         if ((page1_flags & PRESENT_BIT) || (page2_flags & PRESENT_BIT)) {
338                 printf("Pages were made present by MLOCK_ONFAULT\n");
339                 return 1;
340         }
341
342         *map = 'a';
343         page1_flags = get_pageflags((unsigned long)map);
344         page2_flags = get_pageflags((unsigned long)map + page_size);
345
346         /* Only page 1 should be present */
347         if ((page1_flags & PRESENT_BIT) == 0) {
348                 printf("Page 1 is not present after fault\n");
349                 return 1;
350         } else if (page2_flags & PRESENT_BIT) {
351                 printf("Page 2 was made present\n");
352                 return 1;
353         }
354
355         page1_flags = get_kpageflags(page1_flags & PFN_MASK);
356
357         /* Page 1 should be unevictable */
358         if ((page1_flags & UNEVICTABLE_BIT) == 0) {
359                 printf("Failed to make faulted page unevictable\n");
360                 return 1;
361         }
362
363         if (!is_vma_lock_on_fault((unsigned long)map)) {
364                 printf("VMA is not marked for lock on fault\n");
365                 return 1;
366         }
367
368         if (!is_vma_lock_on_fault((unsigned long)map + page_size)) {
369                 printf("VMA is not marked for lock on fault\n");
370                 return 1;
371         }
372
373         return 0;
374 }
375
376 static int unlock_onfault_check(char *map)
377 {
378         unsigned long page_size = getpagesize();
379         uint64_t page1_flags;
380
381         page1_flags = get_pageflags((unsigned long)map);
382         page1_flags = get_kpageflags(page1_flags & PFN_MASK);
383
384         if (page1_flags & UNEVICTABLE_BIT) {
385                 printf("Page 1 is still marked unevictable after unlock\n");
386                 return 1;
387         }
388
389         if (is_vma_lock_on_fault((unsigned long)map) ||
390             is_vma_lock_on_fault((unsigned long)map + page_size)) {
391                 printf("VMA is still lock on fault after unlock\n");
392                 return 1;
393         }
394
395         return 0;
396 }
397
398 static int test_mlock_onfault()
399 {
400         char *map;
401         int ret = 1;
402         unsigned long page_size = getpagesize();
403
404         map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
405                    MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
406         if (map == MAP_FAILED) {
407                 perror("test_mlock_locked mmap");
408                 goto out;
409         }
410
411         if (mlock2_(map, 2 * page_size, MLOCK_ONFAULT)) {
412                 if (errno == ENOSYS) {
413                         printf("Cannot call new mlock family, skipping test\n");
414                         _exit(0);
415                 }
416                 perror("mlock2(MLOCK_ONFAULT)");
417                 goto unmap;
418         }
419
420         if (onfault_check(map))
421                 goto unmap;
422
423         /* Now unlock and recheck attributes */
424         if (munlock(map, 2 * page_size)) {
425                 if (errno == ENOSYS) {
426                         printf("Cannot call new mlock family, skipping test\n");
427                         _exit(0);
428                 }
429                 perror("munlock()");
430                 goto unmap;
431         }
432
433         ret = unlock_onfault_check(map);
434 unmap:
435         munmap(map, 2 * page_size);
436 out:
437         return ret;
438 }
439
440 static int test_lock_onfault_of_present()
441 {
442         char *map;
443         int ret = 1;
444         unsigned long page_size = getpagesize();
445         uint64_t page1_flags, page2_flags;
446
447         map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
448                    MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
449         if (map == MAP_FAILED) {
450                 perror("test_mlock_locked mmap");
451                 goto out;
452         }
453
454         *map = 'a';
455
456         if (mlock2_(map, 2 * page_size, MLOCK_ONFAULT)) {
457                 if (errno == ENOSYS) {
458                         printf("Cannot call new mlock family, skipping test\n");
459                         _exit(0);
460                 }
461                 perror("mlock2(MLOCK_ONFAULT)");
462                 goto unmap;
463         }
464
465         page1_flags = get_pageflags((unsigned long)map);
466         page2_flags = get_pageflags((unsigned long)map + page_size);
467         page1_flags = get_kpageflags(page1_flags & PFN_MASK);
468         page2_flags = get_kpageflags(page2_flags & PFN_MASK);
469
470         /* Page 1 should be unevictable */
471         if ((page1_flags & UNEVICTABLE_BIT) == 0) {
472                 printf("Failed to make present page unevictable\n");
473                 goto unmap;
474         }
475
476         if (!is_vma_lock_on_fault((unsigned long)map) ||
477             !is_vma_lock_on_fault((unsigned long)map + page_size)) {
478                 printf("VMA with present pages is not marked lock on fault\n");
479                 goto unmap;
480         }
481         ret = 0;
482 unmap:
483         munmap(map, 2 * page_size);
484 out:
485         return ret;
486 }
487
488 static int test_munlockall()
489 {
490         char *map;
491         int ret = 1;
492         unsigned long page_size = getpagesize();
493
494         map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
495                    MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
496
497         if (map == MAP_FAILED) {
498                 perror("test_munlockall mmap");
499                 goto out;
500         }
501
502         if (mlockall(MCL_CURRENT)) {
503                 perror("mlockall(MCL_CURRENT)");
504                 goto out;
505         }
506
507         if (lock_check(map))
508                 goto unmap;
509
510         if (munlockall()) {
511                 perror("munlockall()");
512                 goto unmap;
513         }
514
515         if (unlock_lock_check(map))
516                 goto unmap;
517
518         munmap(map, 2 * page_size);
519
520         map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
521                    MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
522
523         if (map == MAP_FAILED) {
524                 perror("test_munlockall second mmap");
525                 goto out;
526         }
527
528         if (mlockall(MCL_CURRENT | MCL_ONFAULT)) {
529                 perror("mlockall(MCL_CURRENT | MCL_ONFAULT)");
530                 goto unmap;
531         }
532
533         if (onfault_check(map))
534                 goto unmap;
535
536         if (munlockall()) {
537                 perror("munlockall()");
538                 goto unmap;
539         }
540
541         if (unlock_onfault_check(map))
542                 goto unmap;
543
544         if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
545                 perror("mlockall(MCL_CURRENT | MCL_FUTURE)");
546                 goto out;
547         }
548
549         if (lock_check(map))
550                 goto unmap;
551
552         if (munlockall()) {
553                 perror("munlockall()");
554                 goto unmap;
555         }
556
557         ret = unlock_lock_check(map);
558
559 unmap:
560         munmap(map, 2 * page_size);
561 out:
562         munlockall();
563         return ret;
564 }
565
566 static int test_vma_management(bool call_mlock)
567 {
568         int ret = 1;
569         void *map;
570         unsigned long page_size = getpagesize();
571         struct vm_boundaries page1;
572         struct vm_boundaries page2;
573         struct vm_boundaries page3;
574
575         map = mmap(NULL, 3 * page_size, PROT_READ | PROT_WRITE,
576                    MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
577         if (map == MAP_FAILED) {
578                 perror("mmap()");
579                 return ret;
580         }
581
582         if (call_mlock && mlock2_(map, 3 * page_size, MLOCK_ONFAULT)) {
583                 if (errno == ENOSYS) {
584                         printf("Cannot call new mlock family, skipping test\n");
585                         _exit(0);
586                 }
587                 perror("mlock(ONFAULT)\n");
588                 goto out;
589         }
590
591         if (get_vm_area((unsigned long)map, &page1) ||
592             get_vm_area((unsigned long)map + page_size, &page2) ||
593             get_vm_area((unsigned long)map + page_size * 2, &page3)) {
594                 printf("couldn't find mapping in /proc/self/maps\n");
595                 goto out;
596         }
597
598         /*
599          * Before we unlock a portion, we need to that all three pages are in
600          * the same VMA.  If they are not we abort this test (Note that this is
601          * not a failure)
602          */
603         if (page1.start != page2.start || page2.start != page3.start) {
604                 printf("VMAs are not merged to start, aborting test\n");
605                 ret = 0;
606                 goto out;
607         }
608
609         if (munlock(map + page_size, page_size)) {
610                 perror("munlock()");
611                 goto out;
612         }
613
614         if (get_vm_area((unsigned long)map, &page1) ||
615             get_vm_area((unsigned long)map + page_size, &page2) ||
616             get_vm_area((unsigned long)map + page_size * 2, &page3)) {
617                 printf("couldn't find mapping in /proc/self/maps\n");
618                 goto out;
619         }
620
621         /* All three VMAs should be different */
622         if (page1.start == page2.start || page2.start == page3.start) {
623                 printf("failed to split VMA for munlock\n");
624                 goto out;
625         }
626
627         /* Now unlock the first and third page and check the VMAs again */
628         if (munlock(map, page_size * 3)) {
629                 perror("munlock()");
630                 goto out;
631         }
632
633         if (get_vm_area((unsigned long)map, &page1) ||
634             get_vm_area((unsigned long)map + page_size, &page2) ||
635             get_vm_area((unsigned long)map + page_size * 2, &page3)) {
636                 printf("couldn't find mapping in /proc/self/maps\n");
637                 goto out;
638         }
639
640         /* Now all three VMAs should be the same */
641         if (page1.start != page2.start || page2.start != page3.start) {
642                 printf("failed to merge VMAs after munlock\n");
643                 goto out;
644         }
645
646         ret = 0;
647 out:
648         munmap(map, 3 * page_size);
649         return ret;
650 }
651
652 static int test_mlockall(int (test_function)(bool call_mlock))
653 {
654         int ret = 1;
655
656         if (mlockall(MCL_CURRENT | MCL_ONFAULT | MCL_FUTURE)) {
657                 perror("mlockall");
658                 return ret;
659         }
660
661         ret = test_function(false);
662         munlockall();
663         return ret;
664 }
665
666 int main(int argc, char **argv)
667 {
668         int ret = 0;
669         ret += test_mlock_lock();
670         ret += test_mlock_onfault();
671         ret += test_munlockall();
672         ret += test_lock_onfault_of_present();
673         ret += test_vma_management(true);
674         ret += test_mlockall(test_vma_management);
675         return ret;
676 }