> Remove the functional modes (GUP_BASIC_TEST, PIN_BASIC_TEST and
> DUMP_USER_PAGES_TEST) from gup_bench. Drop kselftest dependency
> and use normal diagnostics and exit statuses.
> 
> When no arguments are supplied, run a single GUP_FAST_BENCHMARK
> with existing default values. Let users select other configs
> through command line options. Also validate numeric arguments
> and reject positional arguments.
> 
> Restore hugeTLB settings on failure and after every run. Also
> handle failures without relying on assert() calls.
> 
> Suggested-by: David Hildenbrand (Arm) <[email protected]>
> Signed-off-by: Sarthak Sharma <[email protected]>

...

>  int main(int argc, char **argv)
>  {
>       struct gup_test gup = { 0 };
> -     int filed, i, opt, nr_pages = 1, thp = -1, write = 1, nthreads = 1, ret;
> +     int filed, i, opt, nr_pages = 1, thp = -1, write = 1;
> +     int nthreads = 1, ret, started_threads = 0;
>       int flags = MAP_PRIVATE;
> -     char *file = "/dev/zero";
> -     bool hugetlb = false;
> +     const char *file = "/dev/zero";
> +     bool hugetlb = false, restore_hugetlb = false;
> +     unsigned long nr_pages_per_call;
>       pthread_t *tid;
>       char *p;
>  
> -     while ((opt = getopt(argc, argv, "m:r:n:F:f:abcj:tTLUuwWSHpz")) != -1) {
> +     while ((opt = getopt(argc, argv, "m:r:n:F:f:aj:tTLuwWSH")) != -1) {
>               switch (opt) {
>               case 'a':
>                       cmd = PIN_FAST_BENCHMARK;
>                       break;
> -             case 'b':
> -                     cmd = PIN_BASIC_TEST;
> -                     break;
>               case 'L':
>                       cmd = PIN_LONGTERM_BENCHMARK;
>                       break;
> -             case 'c':
> -                     cmd = DUMP_USER_PAGES_TEST;
> -                     /*
> -                      * Dump page 0 (index 1). May be overridden later, by
> -                      * user's non-option arguments.
> -                      *
> -                      * .which_pages is zero-based, so that zero can mean "do
> -                      * nothing".
> -                      */
> -                     gup.which_pages[0] = 1;
> -                     break;
> -             case 'p':
> -                     /* works only with DUMP_USER_PAGES_TEST */
> -                     gup.test_flags |= GUP_TEST_FLAG_DUMP_PAGES_USE_PIN;
> -                     break;
> -             case 'F':
> -                     /* strtol, so you can pass flags in hex form */
> -                     gup.gup_flags = strtol(optarg, 0, 0);
> +             case 'F': {
> +                     long val;
> +
> +                     val = parse_long_arg_base(optarg, "GUP flags", 0);
> +                     if (val < 0 || val > UINT_MAX) {
> +                             fprintf(stderr, "Invalid GUP flags '%s'\n", 
> optarg);
> +                             exit(1);
> +                     }
> +
> +                     gup.gup_flags = val;
>                       break;
> -             case 'j':
> -                     nthreads = atoi(optarg);
> +             }
> +             case 'j': {
> +                     long val;
> +
> +                     val = parse_positive_long_arg(optarg, "thread count");
> +                     if (val > INT_MAX ||
> +                         (size_t)val > SIZE_MAX / sizeof(pthread_t)) {
> +                             fprintf(stderr, "Invalid thread count '%s'\n", 
> optarg);
> +                             exit(1);
> +                     }
> +                     nthreads = val;
>                       break;
> +             }
>               case 'm':
> -                     size = atoi(optarg) * MB;
> +                     size = parse_positive_long_arg(optarg, "size");
> +                     if (size > ULONG_MAX / MB) {
> +                             fprintf(stderr, "Invalid size '%s'\n", optarg);
> +                             exit(1);
> +                     }
> +                     size *= MB;
>                       break;
> -             case 'r':
> -                     repeats = atoi(optarg);
> +             case 'r': {
> +                     long val;
> +
> +                     val = parse_positive_long_arg(optarg, "repeat count");
> +                     if (val > INT_MAX) {
> +                             fprintf(stderr, "Invalid repeat count '%s'\n", 
> optarg);
> +                             exit(1);
> +                     }
> +                     repeats = val;
>                       break;
> -             case 'n':
> -                     nr_pages = atoi(optarg);
> -                     if (nr_pages < 0)
> -                             nr_pages = size / getpagesize();
> +             }
> +             case 'n': {
> +                     long val;
> +
> +                     val = parse_long_arg(optarg, "page count");

It's better to name the numbers parsing after what they do:
parse_flags() and parse_num().

> +                     if (val != -1 && (val < 1 || val > INT_MAX)) {
 
And the limit checks seem wierd all over the place, like if we can loop
infinitely, why do we care about INT_MAX?

And what exact limit ULONG_MAX / MB or SIZE_MAX / sizeof(ptread_t) are
supposed to express?

> +                             fprintf(stderr, "Invalid page count '%s'\n", 
> optarg);
> +                             exit(1);
> +                     }
> +                     nr_pages = val;
>                       break;

...

>       if (hugetlb) {
>               unsigned long hp_size = default_huge_page_size();
>  
> -             if (!hp_size)
> -                     ksft_exit_skip("HugeTLB is unavailable\n");
> +             if (!hp_size) {
> +                     fprintf(stderr, "Could not determine huge page size\n");
> +                     return 1;
> +             }
> +
> +             if (size > ULONG_MAX - (hp_size - 1)) {
> +                     fprintf(stderr, "HugeTLB mapping size is too large\n");
> +                     return 1;
> +             }
>  
>               size = (size + hp_size - 1) & ~(hp_size - 1);
> -             if (!hugetlb_setup_default(size / hp_size))
> -                     ksft_exit_skip("Not enough huge pages\n");
> +             if (!hugetlb_setup_default(size / hp_size)) {
> +                     fprintf(stderr, "Not enough huge pages\n");
> +                     hugetlb_restore_settings();

you don't need to explicitly call hugetlb_restore_settings(),
_setup_defaults() sets up automatic restore on exit.

> +                     return 1;
> +             }
> +             restore_hugetlb = true;
>       }

...

>       gup_fd = open(GUP_TEST_FILE, O_RDWR);
>       if (gup_fd == -1) {
> -             switch (errno) {
> -             case EACCES:
> -                     if (getuid())
> -                             ksft_print_msg("Please run this test as 
> root\n");
> -                     break;
> -             case ENOENT:
> -                     if (opendir("/sys/kernel/debug") == NULL)
> -                             ksft_print_msg("mount debugfs at 
> /sys/kernel/debug\n");
> -                     ksft_print_msg("check if CONFIG_GUP_TEST is enabled in 
> kernel config\n");
> -                     break;
> -             default:
> -                     ksft_print_msg("failed to open %s: %s\n", 
> GUP_TEST_FILE, strerror(errno));
> -                     break;
> -             }
> -             ksft_test_result_skip("Please run this test as root\n");
> -             ksft_exit_pass();
> +             int err = errno;
> +
> +             close(filed);
> +             if (err == EACCES)

What was wrong with switch (errno) ?

> +                     fprintf(stderr, "Please run as root\n");

Please add root check upfront and skip EACCES here

> +             else if (err == ENOENT) {
> +                     DIR *debugfs = opendir("/sys/kernel/debug");
> +
> +                     if (!debugfs)
> +                             fprintf(stderr, "Mount debugfs at 
> /sys/kernel/debug\n");

Just replace the prints, no need to refactor the logic there.

> +                     else {
> +                             closedir(debugfs);
> +                             fprintf(stderr, "Check CONFIG_GUP_TEST in 
> kernel config\n");
> +                     }
> +             } else
> +                     fprintf(stderr, "Failed to open %s: %s\n", 
> GUP_TEST_FILE,
> +                             strerror(err));
> +             if (restore_hugetlb)
> +                     hugetlb_restore_settings();
> +             return 1;
>       }
>  
>       p = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, filed, 0);
> -     if (p == MAP_FAILED)
> -             ksft_exit_fail_msg("mmap: %s\n", strerror(errno));
> +     if (p == MAP_FAILED) {
> +             fprintf(stderr, "mmap: %s\n", strerror(errno));
> +             close(filed);
> +             close(gup_fd);
> +
> +             if (restore_hugetlb)
> +                     hugetlb_restore_settings();

Use goto err_do_cleanup here and everywhere else. Piling cleanups in 
if (something_failed) is error prone and unmaintainable.

> +             return 1;
> +     }
> +     close(filed);
>       gup.addr = (unsigned long)p;
>  
>       if (thp == 1)
  
...

>       free(tid);
> +     munmap((void *)gup.addr, size);
> +     close(gup_fd);
> +     if (restore_hugetlb)
> +             hugetlb_restore_settings();
>  
> -     ksft_exit_pass();
> +     return bench_error ? 1 : 0;

Using goto for cleanup gives you clean return 1 on error and return 0 on
success.

-- 
Sincerely yours,
Mike.


Reply via email to