linux/tools/testing/selftests/vm/mlock2-tests.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2#define _GNU_SOURCE
   3#include <sys/mman.h>
   4#include <stdint.h>
   5#include <unistd.h>
   6#include <string.h>
   7#include <sys/time.h>
   8#include <sys/resource.h>
   9#include <stdbool.h>
  10#include "mlock2.h"
  11
  12#include "../kselftest.h"
  13
  14struct vm_boundaries {
  15        unsigned long start;
  16        unsigned long end;
  17};
  18
  19static int get_vm_area(unsigned long addr, struct vm_boundaries *area)
  20{
  21        FILE *file;
  22        int ret = 1;
  23        char line[1024] = {0};
  24        char *end_addr;
  25        char *stop;
  26        unsigned long start;
  27        unsigned long end;
  28
  29        if (!area)
  30                return ret;
  31
  32        file = fopen("/proc/self/maps", "r");
  33        if (!file) {
  34                perror("fopen");
  35                return ret;
  36        }
  37
  38        memset(area, 0, sizeof(struct vm_boundaries));
  39
  40        while(fgets(line, 1024, file)) {
  41                end_addr = strchr(line, '-');
  42                if (!end_addr) {
  43                        printf("cannot parse /proc/self/maps\n");
  44                        goto out;
  45                }
  46                *end_addr = '\0';
  47                end_addr++;
  48                stop = strchr(end_addr, ' ');
  49                if (!stop) {
  50                        printf("cannot parse /proc/self/maps\n");
  51                        goto out;
  52                }
  53                stop = '\0';
  54
  55                sscanf(line, "%lx", &start);
  56                sscanf(end_addr, "%lx", &end);
  57
  58                if (start <= addr && end > addr) {
  59                        area->start = start;
  60                        area->end = end;
  61                        ret = 0;
  62                        goto out;
  63                }
  64        }
  65out:
  66        fclose(file);
  67        return ret;
  68}
  69
  70#define VMFLAGS "VmFlags:"
  71
  72static bool is_vmflag_set(unsigned long addr, const char *vmflag)
  73{
  74        char *line = NULL;
  75        char *flags;
  76        size_t size = 0;
  77        bool ret = false;
  78        FILE *smaps;
  79
  80        smaps = seek_to_smaps_entry(addr);
  81        if (!smaps) {
  82                printf("Unable to parse /proc/self/smaps\n");
  83                goto out;
  84        }
  85
  86        while (getline(&line, &size, smaps) > 0) {
  87                if (!strstr(line, VMFLAGS)) {
  88                        free(line);
  89                        line = NULL;
  90                        size = 0;
  91                        continue;
  92                }
  93
  94                flags = line + strlen(VMFLAGS);
  95                ret = (strstr(flags, vmflag) != NULL);
  96                goto out;
  97        }
  98
  99out:
 100        free(line);
 101        fclose(smaps);
 102        return ret;
 103}
 104
 105#define SIZE "Size:"
 106#define RSS  "Rss:"
 107#define LOCKED "lo"
 108
 109static unsigned long get_value_for_name(unsigned long addr, const char *name)
 110{
 111        char *line = NULL;
 112        size_t size = 0;
 113        char *value_ptr;
 114        FILE *smaps = NULL;
 115        unsigned long value = -1UL;
 116
 117        smaps = seek_to_smaps_entry(addr);
 118        if (!smaps) {
 119                printf("Unable to parse /proc/self/smaps\n");
 120                goto out;
 121        }
 122
 123        while (getline(&line, &size, smaps) > 0) {
 124                if (!strstr(line, name)) {
 125                        free(line);
 126                        line = NULL;
 127                        size = 0;
 128                        continue;
 129                }
 130
 131                value_ptr = line + strlen(name);
 132                if (sscanf(value_ptr, "%lu kB", &value) < 1) {
 133                        printf("Unable to parse smaps entry for Size\n");
 134                        goto out;
 135                }
 136                break;
 137        }
 138
 139out:
 140        if (smaps)
 141                fclose(smaps);
 142        free(line);
 143        return value;
 144}
 145
 146static bool is_vma_lock_on_fault(unsigned long addr)
 147{
 148        bool locked;
 149        unsigned long vma_size, vma_rss;
 150
 151        locked = is_vmflag_set(addr, LOCKED);
 152        if (!locked)
 153                return false;
 154
 155        vma_size = get_value_for_name(addr, SIZE);
 156        vma_rss = get_value_for_name(addr, RSS);
 157
 158        /* only one page is faulted in */
 159        return (vma_rss < vma_size);
 160}
 161
 162#define PRESENT_BIT     0x8000000000000000ULL
 163#define PFN_MASK        0x007FFFFFFFFFFFFFULL
 164#define UNEVICTABLE_BIT (1UL << 18)
 165
 166static int lock_check(unsigned long addr)
 167{
 168        bool locked;
 169        unsigned long vma_size, vma_rss;
 170
 171        locked = is_vmflag_set(addr, LOCKED);
 172        if (!locked)
 173                return false;
 174
 175        vma_size = get_value_for_name(addr, SIZE);
 176        vma_rss = get_value_for_name(addr, RSS);
 177
 178        return (vma_rss == vma_size);
 179}
 180
 181static int unlock_lock_check(char *map)
 182{
 183        if (is_vmflag_set((unsigned long)map, LOCKED)) {
 184                printf("VMA flag %s is present on page 1 after unlock\n", LOCKED);
 185                return 1;
 186        }
 187
 188        return 0;
 189}
 190
 191static int test_mlock_lock()
 192{
 193        char *map;
 194        int ret = 1;
 195        unsigned long page_size = getpagesize();
 196
 197        map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
 198                   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
 199        if (map == MAP_FAILED) {
 200                perror("test_mlock_locked mmap");
 201                goto out;
 202        }
 203
 204        if (mlock2_(map, 2 * page_size, 0)) {
 205                if (errno == ENOSYS) {
 206                        printf("Cannot call new mlock family, skipping test\n");
 207                        _exit(KSFT_SKIP);
 208                }
 209                perror("mlock2(0)");
 210                goto unmap;
 211        }
 212
 213        if (!lock_check((unsigned long)map))
 214                goto unmap;
 215
 216        /* Now unlock and recheck attributes */
 217        if (munlock(map, 2 * page_size)) {
 218                perror("munlock()");
 219                goto unmap;
 220        }
 221
 222        ret = unlock_lock_check(map);
 223
 224unmap:
 225        munmap(map, 2 * page_size);
 226out:
 227        return ret;
 228}
 229
 230static int onfault_check(char *map)
 231{
 232        *map = 'a';
 233        if (!is_vma_lock_on_fault((unsigned long)map)) {
 234                printf("VMA is not marked for lock on fault\n");
 235                return 1;
 236        }
 237
 238        return 0;
 239}
 240
 241static int unlock_onfault_check(char *map)
 242{
 243        unsigned long page_size = getpagesize();
 244
 245        if (is_vma_lock_on_fault((unsigned long)map) ||
 246            is_vma_lock_on_fault((unsigned long)map + page_size)) {
 247                printf("VMA is still lock on fault after unlock\n");
 248                return 1;
 249        }
 250
 251        return 0;
 252}
 253
 254static int test_mlock_onfault()
 255{
 256        char *map;
 257        int ret = 1;
 258        unsigned long page_size = getpagesize();
 259
 260        map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
 261                   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
 262        if (map == MAP_FAILED) {
 263                perror("test_mlock_locked mmap");
 264                goto out;
 265        }
 266
 267        if (mlock2_(map, 2 * page_size, MLOCK_ONFAULT)) {
 268                if (errno == ENOSYS) {
 269                        printf("Cannot call new mlock family, skipping test\n");
 270                        _exit(KSFT_SKIP);
 271                }
 272                perror("mlock2(MLOCK_ONFAULT)");
 273                goto unmap;
 274        }
 275
 276        if (onfault_check(map))
 277                goto unmap;
 278
 279        /* Now unlock and recheck attributes */
 280        if (munlock(map, 2 * page_size)) {
 281                if (errno == ENOSYS) {
 282                        printf("Cannot call new mlock family, skipping test\n");
 283                        _exit(KSFT_SKIP);
 284                }
 285                perror("munlock()");
 286                goto unmap;
 287        }
 288
 289        ret = unlock_onfault_check(map);
 290unmap:
 291        munmap(map, 2 * page_size);
 292out:
 293        return ret;
 294}
 295
 296static int test_lock_onfault_of_present()
 297{
 298        char *map;
 299        int ret = 1;
 300        unsigned long page_size = getpagesize();
 301
 302        map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
 303                   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
 304        if (map == MAP_FAILED) {
 305                perror("test_mlock_locked mmap");
 306                goto out;
 307        }
 308
 309        *map = 'a';
 310
 311        if (mlock2_(map, 2 * page_size, MLOCK_ONFAULT)) {
 312                if (errno == ENOSYS) {
 313                        printf("Cannot call new mlock family, skipping test\n");
 314                        _exit(KSFT_SKIP);
 315                }
 316                perror("mlock2(MLOCK_ONFAULT)");
 317                goto unmap;
 318        }
 319
 320        if (!is_vma_lock_on_fault((unsigned long)map) ||
 321            !is_vma_lock_on_fault((unsigned long)map + page_size)) {
 322                printf("VMA with present pages is not marked lock on fault\n");
 323                goto unmap;
 324        }
 325        ret = 0;
 326unmap:
 327        munmap(map, 2 * page_size);
 328out:
 329        return ret;
 330}
 331
 332static int test_munlockall()
 333{
 334        char *map;
 335        int ret = 1;
 336        unsigned long page_size = getpagesize();
 337
 338        map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
 339                   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
 340
 341        if (map == MAP_FAILED) {
 342                perror("test_munlockall mmap");
 343                goto out;
 344        }
 345
 346        if (mlockall(MCL_CURRENT)) {
 347                perror("mlockall(MCL_CURRENT)");
 348                goto out;
 349        }
 350
 351        if (!lock_check((unsigned long)map))
 352                goto unmap;
 353
 354        if (munlockall()) {
 355                perror("munlockall()");
 356                goto unmap;
 357        }
 358
 359        if (unlock_lock_check(map))
 360                goto unmap;
 361
 362        munmap(map, 2 * page_size);
 363
 364        map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
 365                   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
 366
 367        if (map == MAP_FAILED) {
 368                perror("test_munlockall second mmap");
 369                goto out;
 370        }
 371
 372        if (mlockall(MCL_CURRENT | MCL_ONFAULT)) {
 373                perror("mlockall(MCL_CURRENT | MCL_ONFAULT)");
 374                goto unmap;
 375        }
 376
 377        if (onfault_check(map))
 378                goto unmap;
 379
 380        if (munlockall()) {
 381                perror("munlockall()");
 382                goto unmap;
 383        }
 384
 385        if (unlock_onfault_check(map))
 386                goto unmap;
 387
 388        if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
 389                perror("mlockall(MCL_CURRENT | MCL_FUTURE)");
 390                goto out;
 391        }
 392
 393        if (!lock_check((unsigned long)map))
 394                goto unmap;
 395
 396        if (munlockall()) {
 397                perror("munlockall()");
 398                goto unmap;
 399        }
 400
 401        ret = unlock_lock_check(map);
 402
 403unmap:
 404        munmap(map, 2 * page_size);
 405out:
 406        munlockall();
 407        return ret;
 408}
 409
 410static int test_vma_management(bool call_mlock)
 411{
 412        int ret = 1;
 413        void *map;
 414        unsigned long page_size = getpagesize();
 415        struct vm_boundaries page1;
 416        struct vm_boundaries page2;
 417        struct vm_boundaries page3;
 418
 419        map = mmap(NULL, 3 * page_size, PROT_READ | PROT_WRITE,
 420                   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
 421        if (map == MAP_FAILED) {
 422                perror("mmap()");
 423                return ret;
 424        }
 425
 426        if (call_mlock && mlock2_(map, 3 * page_size, MLOCK_ONFAULT)) {
 427                if (errno == ENOSYS) {
 428                        printf("Cannot call new mlock family, skipping test\n");
 429                        _exit(KSFT_SKIP);
 430                }
 431                perror("mlock(ONFAULT)\n");
 432                goto out;
 433        }
 434
 435        if (get_vm_area((unsigned long)map, &page1) ||
 436            get_vm_area((unsigned long)map + page_size, &page2) ||
 437            get_vm_area((unsigned long)map + page_size * 2, &page3)) {
 438                printf("couldn't find mapping in /proc/self/maps\n");
 439                goto out;
 440        }
 441
 442        /*
 443         * Before we unlock a portion, we need to that all three pages are in
 444         * the same VMA.  If they are not we abort this test (Note that this is
 445         * not a failure)
 446         */
 447        if (page1.start != page2.start || page2.start != page3.start) {
 448                printf("VMAs are not merged to start, aborting test\n");
 449                ret = 0;
 450                goto out;
 451        }
 452
 453        if (munlock(map + page_size, page_size)) {
 454                perror("munlock()");
 455                goto out;
 456        }
 457
 458        if (get_vm_area((unsigned long)map, &page1) ||
 459            get_vm_area((unsigned long)map + page_size, &page2) ||
 460            get_vm_area((unsigned long)map + page_size * 2, &page3)) {
 461                printf("couldn't find mapping in /proc/self/maps\n");
 462                goto out;
 463        }
 464
 465        /* All three VMAs should be different */
 466        if (page1.start == page2.start || page2.start == page3.start) {
 467                printf("failed to split VMA for munlock\n");
 468                goto out;
 469        }
 470
 471        /* Now unlock the first and third page and check the VMAs again */
 472        if (munlock(map, page_size * 3)) {
 473                perror("munlock()");
 474                goto out;
 475        }
 476
 477        if (get_vm_area((unsigned long)map, &page1) ||
 478            get_vm_area((unsigned long)map + page_size, &page2) ||
 479            get_vm_area((unsigned long)map + page_size * 2, &page3)) {
 480                printf("couldn't find mapping in /proc/self/maps\n");
 481                goto out;
 482        }
 483
 484        /* Now all three VMAs should be the same */
 485        if (page1.start != page2.start || page2.start != page3.start) {
 486                printf("failed to merge VMAs after munlock\n");
 487                goto out;
 488        }
 489
 490        ret = 0;
 491out:
 492        munmap(map, 3 * page_size);
 493        return ret;
 494}
 495
 496static int test_mlockall(int (test_function)(bool call_mlock))
 497{
 498        int ret = 1;
 499
 500        if (mlockall(MCL_CURRENT | MCL_ONFAULT | MCL_FUTURE)) {
 501                perror("mlockall");
 502                return ret;
 503        }
 504
 505        ret = test_function(false);
 506        munlockall();
 507        return ret;
 508}
 509
 510int main(int argc, char **argv)
 511{
 512        int ret = 0;
 513        ret += test_mlock_lock();
 514        ret += test_mlock_onfault();
 515        ret += test_munlockall();
 516        ret += test_lock_onfault_of_present();
 517        ret += test_vma_management(true);
 518        ret += test_mlockall(test_vma_management);
 519        return ret;
 520}
 521