/mandos/release

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/release

« back to all changes in this revision

Viewing changes to dracut-module/password-agent.c

  • Committer: Teddy Hogeborn
  • Date: 2019-07-29 16:35:53 UTC
  • mto: This revision was merged to the branch mainline in revision 384.
  • Revision ID: teddy@recompile.se-20190729163553-1i442i2cbx64c537
Make tests and man page examples match

Make the tests test_manual_page_example[1-5] match exactly what is
written in the manual page, and add comments to manual page as
reminders to keep tests and manual page examples in sync.

* mandos-ctl (Test_commands_from_options.test_manual_page_example_1):
  Remove "--verbose" option, since the manual does not have it as the
  first example, and change assertion to match.
* mandos-ctl.xml (EXAMPLE): Add comments to all examples documenting
  which test function they correspond to.  Also remove unnecessary
  quotes from option arguments in fourth example, and clarify language
  slightly in fifth example.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* -*- coding: utf-8; lexical-binding: t -*- */
 
1
/* -*- mode: c; coding: utf-8; after-save-hook: (lambda () (let* ((find-build-directory (lambda (try-directory &optional base-directory) (let ((base-directory (or base-directory try-directory))) (cond ((equal try-directory "/") base-directory) ((file-readable-p (concat (file-name-as-directory try-directory) "Makefile")) try-directory) ((funcall find-build-directory (directory-file-name (file-name-directory try-directory)) base-directory)))))) (build-directory (funcall find-build-directory (buffer-file-name))) (local-build-directory (if (fboundp 'file-local-name) (file-local-name build-directory) (or (file-remote-p build-directory 'localname) build-directory))) (command (file-relative-name (file-name-sans-extension (buffer-file-name)) build-directory))) (pcase (progn (if (get-buffer "*Test*") (kill-buffer "*Test*")) (process-file-shell-command (let ((qbdir (shell-quote-argument local-build-directory)) (qcmd (shell-quote-argument command))) (format "cd %s && CFLAGS=-Werror make --silent %s && %s --test --verbose" qbdir qcmd qcmd)) nil "*Test*")) (0 (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w)))) (_ (with-current-buffer "*Test*" (compilation-mode) (cd-absolute build-directory)) (display-buffer "*Test*" '(display-buffer-in-side-window)))))); -*- */
2
2
/*
3
3
 * Mandos password agent - Simple password agent to run Mandos client
4
4
 *
5
 
 * Copyright © 2019-2022 Teddy Hogeborn
6
 
 * Copyright © 2019-2022 Björn Påhlsson
 
5
 * Copyright © 2019 Teddy Hogeborn
 
6
 * Copyright © 2019 Björn Påhlsson
7
7
 * 
8
8
 * This file is part of Mandos.
9
9
 * 
23
23
 * Contact the authors at <mandos@recompile.se>.
24
24
 */
25
25
 
26
 
#define _GNU_SOURCE             /* pipe2(), O_CLOEXEC, setresgid(),
27
 
                                   setresuid(), asprintf(), getline(),
28
 
                                   basename() */
29
 
#include <inttypes.h>           /* uintmax_t, strtoumax(), PRIuMAX,
30
 
                                   PRIdMAX, intmax_t, uint32_t,
31
 
                                   SCNx32, SCNuMAX, SCNxMAX */
32
 
#include <stddef.h>             /* size_t, NULL */
 
26
#define _GNU_SOURCE
 
27
#include <inttypes.h>           /* uintmax_t, PRIuMAX, PRIdMAX,
 
28
                                   intmax_t, uint32_t, SCNx32,
 
29
                                   SCNuMAX, SCNxMAX */
 
30
#include <stddef.h>             /* size_t */
33
31
#include <sys/types.h>          /* pid_t, uid_t, gid_t, getuid(),
34
32
                                   getpid() */
35
33
#include <stdbool.h>            /* bool, true, false */
42
40
                                   NSIG, sigismember(), SA_ONSTACK,
43
41
                                   SIG_DFL, SIG_IGN, SIGINT, SIGQUIT,
44
42
                                   SIGHUP, SIGSTOP, SIG_UNBLOCK */
45
 
#include <unistd.h>             /* uid_t, gid_t, close(), pipe2(),
46
 
                                   fork(), _exit(), dup2(),
47
 
                                   STDOUT_FILENO, setresgid(),
48
 
                                   setresuid(), execv(), ssize_t,
49
 
                                   read(), dup3(), getuid(), dup(),
50
 
                                   STDERR_FILENO, pause(), write(),
51
 
                                   rmdir(), unlink(), getpid() */
52
43
#include <stdlib.h>             /* EXIT_SUCCESS, EXIT_FAILURE,
53
 
                                   malloc(), free(), realloc(),
54
 
                                   setenv(), calloc(), mkdtemp(),
55
 
                                   mkostemp() */
 
44
                                   malloc(), free(), strtoumax(),
 
45
                                   realloc(), setenv(), calloc(),
 
46
                                   mkdtemp(), mkostemp() */
56
47
#include <iso646.h>             /* not, or, and, xor */
57
48
#include <error.h>              /* error() */
58
49
#include <sysexits.h>           /* EX_USAGE, EX_OSERR, EX_OSFILE */
59
50
#include <errno.h>              /* errno, error_t, EACCES,
60
 
                                   ENAMETOOLONG, ENOENT, ENOTDIR,
61
 
                                   ENOMEM, EEXIST, ECHILD, EPERM,
62
 
                                   EAGAIN, EINTR, ENOBUFS, EADDRINUSE,
 
51
                                   ENAMETOOLONG, ENOENT, EEXIST,
 
52
                                   ECHILD, EPERM, ENOMEM, EAGAIN,
 
53
                                   EINTR, ENOBUFS, EADDRINUSE,
63
54
                                   ECONNREFUSED, ECONNRESET,
64
55
                                   ETOOMANYREFS, EMSGSIZE, EBADF,
65
56
                                   EINVAL */
66
57
#include <string.h>             /* strdup(), memcpy(),
67
58
                                   explicit_bzero(), memset(),
68
59
                                   strcmp(), strlen(), strncpy(),
69
 
                                   memcmp(), basename(), strerror() */
 
60
                                   memcmp(), basename() */
70
61
#include <argz.h>               /* argz_create(), argz_count(),
71
62
                                   argz_extract(), argz_next(),
72
63
                                   argz_add() */
82
73
                                   ARGP_ERR_UNKNOWN, ARGP_KEY_ARGS,
83
74
                                   struct argp, argp_parse(),
84
75
                                   ARGP_NO_EXIT */
85
 
#include <stdint.h>             /* SIZE_MAX, uint32_t */
 
76
#include <unistd.h>             /* uid_t, gid_t, close(), pipe2(),
 
77
                                   fork(), _exit(), dup2(),
 
78
                                   STDOUT_FILENO, setresgid(),
 
79
                                   setresuid(), execv(), ssize_t,
 
80
                                   read(), dup3(), getuid(), dup(),
 
81
                                   STDERR_FILENO, pause(), write(),
 
82
                                   rmdir(), unlink(), getpid() */
86
83
#include <sys/mman.h>           /* munlock(), mlock() */
87
84
#include <fcntl.h>              /* O_CLOEXEC, O_NONBLOCK, fcntl(),
88
85
                                   F_GETFD, F_GETFL, FD_CLOEXEC,
89
 
                                   open(), O_WRONLY, O_NOCTTY,
90
 
                                   O_RDONLY, O_NOFOLLOW */
 
86
                                   open(), O_WRONLY, O_RDONLY */
91
87
#include <sys/wait.h>           /* waitpid(), WNOHANG, WIFEXITED(),
92
88
                                   WEXITSTATUS() */
93
89
#include <limits.h>             /* PIPE_BUF, NAME_MAX, INT_MAX */
94
90
#include <sys/inotify.h>        /* inotify_init1(), IN_NONBLOCK,
95
91
                                   IN_CLOEXEC, inotify_add_watch(),
96
92
                                   IN_CLOSE_WRITE, IN_MOVED_TO,
97
 
                                   IN_MOVED_FROM, IN_DELETE,
98
 
                                   IN_EXCL_UNLINK, IN_ONLYDIR,
99
 
                                   struct inotify_event */
 
93
                                   IN_DELETE, struct inotify_event */
100
94
#include <fnmatch.h>            /* fnmatch(), FNM_FILE_NAME */
101
 
#include <stdio.h>              /* asprintf(), FILE, stderr, fopen(),
102
 
                                   fclose(), getline(), sscanf(),
103
 
                                   feof(), ferror(), rename(),
104
 
                                   fdopen(), fprintf(), fscanf() */
 
95
#include <stdio.h>              /* asprintf(), FILE, fopen(),
 
96
                                   getline(), sscanf(), feof(),
 
97
                                   ferror(), fclose(), stderr,
 
98
                                   rename(), fdopen(), fprintf(),
 
99
                                   fscanf() */
105
100
#include <glib.h>    /* GKeyFile, g_key_file_free(), g_key_file_new(),
106
101
                        GError, g_key_file_load_from_file(),
107
102
                        G_KEY_FILE_NONE, TRUE, G_FILE_ERROR_NOENT,
112
107
                        g_assert_null(), g_assert_false(),
113
108
                        g_assert_cmpint(), g_assert_cmpuint(),
114
109
                        g_test_skip(), g_assert_cmpstr(),
115
 
                        g_test_message(), g_test_init(), g_test_add(),
116
 
                        g_test_run(), GOptionContext,
117
 
                        g_option_context_new(),
 
110
                        g_test_init(), g_test_add(), g_test_run(),
 
111
                        GOptionContext, g_option_context_new(),
118
112
                        g_option_context_set_help_enabled(), FALSE,
119
113
                        g_option_context_set_ignore_unknown_options(),
120
114
                        gboolean, GOptionEntry, G_OPTION_ARG_NONE,
151
145
  mono_microsecs next_run;
152
146
} __attribute__((designated_init)) task_queue;
153
147
 
154
 
/* "task_func" - A function type for task functions
 
148
/* "func_type" - A function type for task functions
155
149
 
156
150
   I.e. functions for the code which runs when a task is run, all have
157
151
   this type */
437
431
    case EACCES:
438
432
    case ENAMETOOLONG:
439
433
    case ENOENT:
440
 
    case ENOTDIR:
441
434
      return EX_OSFILE;
442
435
    default:
443
436
      return EX_OSERR;
654
647
 
655
648
__attribute__((nonnull, warn_unused_result))
656
649
bool add_to_queue(task_queue *const queue, const task_context task){
657
 
  if((queue->length + 1) > (SIZE_MAX / sizeof(task_context))){
658
 
    /* overflow */
659
 
    error(0, ENOMEM, "Failed to allocate %" PRIuMAX
660
 
          " tasks for queue->tasks", (uintmax_t)(queue->length + 1));
661
 
    errno = ENOMEM;
662
 
    return false;
663
 
  }
664
650
  const size_t needed_size = sizeof(task_context)*(queue->length + 1);
665
651
  if(needed_size > (queue->allocated)){
666
652
    task_context *const new_tasks = realloc(queue->tasks,
871
857
  }
872
858
  close(pipefds[1]);
873
859
 
874
 
  if(pid == -1){
875
 
    error(0, errno, "Failed to fork()");
876
 
    close(pipefds[0]);
877
 
    return false;
878
 
  }
879
 
 
880
860
  if(not add_to_queue(queue, (task_context){
881
861
        .func=wait_for_mandos_client_exit,
882
862
        .pid=pid,
1037
1017
    return false;
1038
1018
  }
1039
1019
 
1040
 
  if(inotify_add_watch(fd, dir, IN_CLOSE_WRITE | IN_MOVED_TO
1041
 
                       | IN_MOVED_FROM| IN_DELETE | IN_EXCL_UNLINK
1042
 
                       | IN_ONLYDIR)
 
1020
  if(inotify_add_watch(fd, dir, IN_CLOSE_WRITE
 
1021
                       | IN_MOVED_TO | IN_DELETE)
1043
1022
     == -1){
1044
1023
    error(0, errno, "Failed to create inotify watch on %s", dir);
1045
1024
    return false;
1092
1071
  /* "sufficient to read at least one event." - inotify(7) */
1093
1072
  const size_t ievent_size = (sizeof(struct inotify_event)
1094
1073
                              + NAME_MAX + 1);
1095
 
  struct {
1096
 
    struct inotify_event event;
1097
 
    char name_buffer[NAME_MAX + 1];
1098
 
  } ievent_buffer;
1099
 
  struct inotify_event *const ievent = &ievent_buffer.event;
 
1074
  char ievent_buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
 
1075
  struct inotify_event *ievent = ((struct inotify_event *)
 
1076
                                  ievent_buffer);
1100
1077
 
1101
 
#if defined(__GNUC__) and __GNUC__ >= 7
1102
 
#pragma GCC diagnostic push
1103
 
  /* ievent is pointing into a struct which is of sufficient size */
1104
 
#pragma GCC diagnostic ignored "-Wstringop-overflow"
1105
 
#endif
1106
1078
  const ssize_t read_length = read(fd, ievent, ievent_size);
1107
 
#if defined(__GNUC__) and __GNUC__ >= 7
1108
 
#pragma GCC diagnostic pop
1109
 
#endif
1110
1079
  if(read_length == 0){ /* EOF */
1111
1080
    error(0, 0, "Got EOF from inotify fd for directory %s", filename);
1112
1081
    *quit_now = true;
1148
1117
             immediately */
1149
1118
          queue->next_run = 1;
1150
1119
        }
1151
 
      } else if(ievent->mask & (IN_MOVED_FROM | IN_DELETE)){
 
1120
      } else if(ievent->mask & IN_DELETE){
1152
1121
        if(not string_set_add(cancelled_filenames,
1153
1122
                              question_filename)){
1154
1123
          error(0, errno, "Could not add question %s to"
1204
1173
  bool *const password_is_read = task.password_is_read;
1205
1174
 
1206
1175
  /* We use the GLib "Key-value file parser" functions to parse the
1207
 
     question file.  See <https://systemd.io/PASSWORD_AGENTS/> for
1208
 
     specification of contents */
 
1176
     question file.  See <https://www.freedesktop.org/wiki/Software
 
1177
     /systemd/PasswordAgents/> for specification of contents */
1209
1178
  __attribute__((nonnull))
1210
1179
    void cleanup_g_key_file(GKeyFile **key_file){
1211
1180
    if(*key_file != NULL){
1488
1457
    if(send_buffer == NULL){
1489
1458
      error(0, errno, "Failed to allocate send_buffer");
1490
1459
    } else {
1491
 
#if defined(__GNUC__) and __GNUC__ >= 5
1492
 
#pragma GCC diagnostic push
1493
 
  /* mlock() does not access the memory */
1494
 
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
1495
 
#endif
1496
1460
      if(mlock(send_buffer, send_buffer_length) != 0){
1497
 
#if defined(__GNUC__) and __GNUC__ >= 5
1498
 
#pragma GCC diagnostic pop
1499
 
#endif
1500
1461
        /* Warn but do not treat as fatal error */
1501
1462
        if(errno != EPERM and errno != ENOMEM){
1502
1463
          error(0, errno, "Failed to lock memory for password"
1509
1470
         not. You may but don't have to include a final NUL byte in
1510
1471
         your message.
1511
1472
 
1512
 
         — <https://systemd.io/PASSWORD_AGENTS/> (Tue, 15 Sep 2020
1513
 
         14:24:20 GMT)
 
1473
         — <https://www.freedesktop.org/wiki/Software/systemd/
 
1474
         PasswordAgents/> (Wed 08 Oct 2014 02:14:28 AM UTC)
1514
1475
      */
1515
1476
      send_buffer[0] = '+';     /* Prefix with "+" */
1516
1477
      /* Always add an extra NUL */
1521
1482
      errno = 0;
1522
1483
      ssize_t ssret = send(fd, send_buffer, send_buffer_length,
1523
1484
                           MSG_NOSIGNAL);
1524
 
      const error_t saved_errno = (ssret < 0) ? errno : 0;
 
1485
      const error_t saved_errno = errno;
1525
1486
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 25)
1526
1487
      explicit_bzero(send_buffer, send_buffer_length);
1527
1488
#else
1545
1506
          /* Retry, below */
1546
1507
          break;
1547
1508
        case EMSGSIZE:
1548
 
          error(0, saved_errno, "Password of size %" PRIuMAX
1549
 
                " is too big", (uintmax_t)password->length);
 
1509
          error(0, 0, "Password of size %" PRIuMAX " is too big",
 
1510
                (uintmax_t)password->length);
1550
1511
#if __GNUC__ < 7
1551
1512
          /* FALLTHROUGH */
1552
1513
#else
1554
1515
#endif
1555
1516
        case 0:
1556
1517
          if(ssret >= 0 and ssret < (ssize_t)send_buffer_length){
1557
 
            error(0, 0, "Password only partially sent to socket %s: %"
1558
 
                  PRIuMAX " out of %" PRIuMAX " bytes sent", filename,
1559
 
                  (uintmax_t)ssret, (uintmax_t)send_buffer_length);
 
1518
            error(0, 0, "Password only partially sent to socket");
1560
1519
          }
1561
1520
#if __GNUC__ < 7
1562
1521
          /* FALLTHROUGH */
1918
1877
  g_assert_true(queue->tasks[0].func == dummy_func);
1919
1878
}
1920
1879
 
1921
 
static void test_add_to_queue_overflow(__attribute__((unused))
1922
 
                                       test_fixture *fixture,
1923
 
                                       __attribute__((unused))
1924
 
                                       gconstpointer user_data){
1925
 
  __attribute__((cleanup(cleanup_queue)))
1926
 
    task_queue *queue = create_queue();
1927
 
  g_assert_nonnull(queue);
1928
 
  g_assert_true(queue->length == 0);
1929
 
  queue->length = SIZE_MAX / sizeof(task_context); /* fake max size */
1930
 
 
1931
 
  FILE *real_stderr = stderr;
1932
 
  FILE *devnull = fopen("/dev/null", "we");
1933
 
  g_assert_nonnull(devnull);
1934
 
  stderr = devnull;
1935
 
  const bool ret = add_to_queue(queue,
1936
 
                                (task_context){ .func=dummy_func });
1937
 
  g_assert_true(errno == ENOMEM);
1938
 
  g_assert_false(ret);
1939
 
  stderr = real_stderr;
1940
 
  g_assert_cmpint(fclose(devnull), ==, 0);
1941
 
  queue->length = 0;            /* Restore real size */
1942
 
}
1943
 
 
1944
1880
static void dummy_func(__attribute__((unused))
1945
1881
                       const task_context task,
1946
1882
                       __attribute__((unused))
2217
2153
    }
2218
2154
    exit(EXIT_SUCCESS);
2219
2155
  }
2220
 
  if(pid == -1){
2221
 
    error(EXIT_FAILURE, errno, "Failed to fork()");
2222
 
  }
2223
 
 
2224
2156
  int status;
2225
2157
  waitpid(pid, &status, 0);
2226
2158
  if(WIFEXITED(status) and (WEXITSTATUS(status) == EXIT_SUCCESS)){
2288
2220
 
2289
2221
  {
2290
2222
    __attribute__((cleanup(cleanup_close)))
2291
 
      const int devnull_fd = open("/dev/null",
2292
 
                                  O_WRONLY | O_CLOEXEC | O_NOCTTY);
 
2223
      const int devnull_fd = open("/dev/null", O_WRONLY | O_CLOEXEC);
2293
2224
    g_assert_cmpint(devnull_fd, >=, 0);
2294
2225
    __attribute__((cleanup(cleanup_close)))
2295
2226
      const int real_stderr_fd = dup(STDERR_FILENO);
2319
2250
    {
2320
2251
      __attribute__((cleanup(cleanup_close)))
2321
2252
        const int devnull_fd = open("/dev/null",
2322
 
                                    O_WRONLY | O_CLOEXEC | O_NOCTTY);
 
2253
                                    O_WRONLY | O_CLOEXEC);
2323
2254
      g_assert_cmpint(devnull_fd, >=, 0);
2324
2255
      __attribute__((cleanup(cleanup_close)))
2325
2256
        const int real_stderr_fd = dup(STDERR_FILENO);
2669
2600
  bool password_is_read = false;
2670
2601
  const char helper_directory[] = "/nonexistent";
2671
2602
  const char *const argv[] = { "/bin/sh", "-c",
2672
 
    "printf %s \"${MANDOSPLUGINHELPERDIR}\"", NULL };
 
2603
    "echo -n ${MANDOSPLUGINHELPERDIR}", NULL };
2673
2604
 
2674
2605
  const bool success = start_mandos_client(queue, epoll_fd,
2675
2606
                                           &mandos_client_exited,
2970
2901
 
2971
2902
  __attribute__((cleanup(cleanup_close)))
2972
2903
    const int devnull_fd = open("/dev/null",
2973
 
                                O_WRONLY | O_CLOEXEC | O_NOCTTY);
 
2904
                                O_WRONLY | O_CLOEXEC);
2974
2905
  g_assert_cmpint(devnull_fd, >=, 0);
2975
2906
  __attribute__((cleanup(cleanup_close)))
2976
2907
    const int real_stderr_fd = dup(STDERR_FILENO);
3041
2972
 
3042
2973
  __attribute__((cleanup(cleanup_close)))
3043
2974
    const int devnull_fd = open("/dev/null",
3044
 
                                O_WRONLY | O_CLOEXEC, O_NOCTTY);
 
2975
                                O_WRONLY | O_CLOEXEC);
3045
2976
  g_assert_cmpint(devnull_fd, >=, 0);
3046
2977
  __attribute__((cleanup(cleanup_close)))
3047
2978
    const int real_stderr_fd = dup(STDERR_FILENO);
3085
3016
    buffer password = {};
3086
3017
 
3087
3018
  /* Reading /proc/self/mem from offset 0 will always give EIO */
3088
 
  const int fd = open("/proc/self/mem",
3089
 
                      O_RDONLY | O_CLOEXEC | O_NOCTTY);
 
3019
  const int fd = open("/proc/self/mem", O_RDONLY | O_CLOEXEC);
3090
3020
 
3091
3021
  bool password_is_read = false;
3092
3022
  bool quit_now = false;
3520
3450
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
3521
3451
}
3522
3452
 
3523
 
static void test_add_inotify_dir_watch_nondir(__attribute__((unused))
3524
 
                                              test_fixture *fixture,
3525
 
                                            __attribute__((unused))
3526
 
                                              gconstpointer
3527
 
                                              user_data){
3528
 
  __attribute__((cleanup(cleanup_close)))
3529
 
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3530
 
  g_assert_cmpint(epoll_fd, >=, 0);
3531
 
  __attribute__((cleanup(cleanup_queue)))
3532
 
    task_queue *queue = create_queue();
3533
 
  g_assert_nonnull(queue);
3534
 
  __attribute__((cleanup(string_set_clear)))
3535
 
    string_set cancelled_filenames = {};
3536
 
  const mono_microsecs current_time = 0;
3537
 
 
3538
 
  bool quit_now = false;
3539
 
  buffer password = {};
3540
 
  bool mandos_client_exited = false;
3541
 
  bool password_is_read = false;
3542
 
 
3543
 
  const char not_a_directory[] = "/dev/tty";
3544
 
 
3545
 
  FILE *real_stderr = stderr;
3546
 
  FILE *devnull = fopen("/dev/null", "we");
3547
 
  g_assert_nonnull(devnull);
3548
 
  stderr = devnull;
3549
 
  g_assert_false(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
3550
 
                                       &password, not_a_directory,
3551
 
                                       &cancelled_filenames,
3552
 
                                       &current_time,
3553
 
                                       &mandos_client_exited,
3554
 
                                       &password_is_read));
3555
 
  stderr = real_stderr;
3556
 
  g_assert_cmpint(fclose(devnull), ==, 0);
3557
 
 
3558
 
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
3559
 
}
3560
 
 
3561
3453
static void test_add_inotify_dir_watch_EAGAIN(__attribute__((unused))
3562
3454
                                              test_fixture *fixture,
3563
3455
                                              __attribute__((unused))
3778
3670
}
3779
3671
 
3780
3672
static
3781
 
void test_add_inotify_dir_watch_IN_MOVED_FROM(__attribute__((unused))
3782
 
                                              test_fixture *fixture,
3783
 
                                              __attribute__((unused))
3784
 
                                              gconstpointer
3785
 
                                              user_data){
3786
 
  __attribute__((cleanup(cleanup_close)))
3787
 
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3788
 
  g_assert_cmpint(epoll_fd, >=, 0);
3789
 
  __attribute__((cleanup(cleanup_queue)))
3790
 
    task_queue *queue = create_queue();
3791
 
  g_assert_nonnull(queue);
3792
 
  __attribute__((cleanup(string_set_clear)))
3793
 
    string_set cancelled_filenames = {};
3794
 
  const mono_microsecs current_time = 0;
3795
 
 
3796
 
  bool quit_now = false;
3797
 
  buffer password = {};
3798
 
  bool mandos_client_exited = false;
3799
 
  bool password_is_read = false;
3800
 
 
3801
 
  __attribute__((cleanup(cleanup_string)))
3802
 
    char *tempdir = make_temporary_directory();
3803
 
  g_assert_nonnull(tempdir);
3804
 
 
3805
 
  __attribute__((cleanup(cleanup_string)))
3806
 
    char *tempfilename = make_temporary_file_in_directory(tempdir);
3807
 
  g_assert_nonnull(tempfilename);
3808
 
 
3809
 
  __attribute__((cleanup(cleanup_string)))
3810
 
    char *targetdir = make_temporary_directory();
3811
 
  g_assert_nonnull(targetdir);
3812
 
 
3813
 
  __attribute__((cleanup(cleanup_string)))
3814
 
    char *targetfilename = NULL;
3815
 
  g_assert_cmpint(asprintf(&targetfilename, "%s/%s", targetdir,
3816
 
                           basename(tempfilename)), >, 0);
3817
 
  g_assert_nonnull(targetfilename);
3818
 
 
3819
 
  g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
3820
 
                                      &password, tempdir,
3821
 
                                      &cancelled_filenames,
3822
 
                                      &current_time,
3823
 
                                      &mandos_client_exited,
3824
 
                                      &password_is_read));
3825
 
 
3826
 
  g_assert_cmpint(rename(tempfilename, targetfilename), ==, 0);
3827
 
 
3828
 
  const task_context *const added_read_task
3829
 
    = find_matching_task(queue,
3830
 
                         (task_context){ .func=read_inotify_event });
3831
 
  g_assert_nonnull(added_read_task);
3832
 
 
3833
 
  /* "sufficient to read at least one event." - inotify(7) */
3834
 
  const size_t ievent_size = (sizeof(struct inotify_event)
3835
 
                              + NAME_MAX + 1);
3836
 
  struct inotify_event *ievent = malloc(ievent_size);
3837
 
  g_assert_nonnull(ievent);
3838
 
 
3839
 
  ssize_t read_size = read(added_read_task->fd, ievent, ievent_size);
3840
 
 
3841
 
  g_assert_cmpint((int)read_size, >, 0);
3842
 
  g_assert_true(ievent->mask & IN_MOVED_FROM);
3843
 
  g_assert_cmpstr(ievent->name, ==, basename(tempfilename));
3844
 
 
3845
 
  free(ievent);
3846
 
 
3847
 
  g_assert_cmpint(unlink(targetfilename), ==, 0);
3848
 
  g_assert_cmpint(rmdir(targetdir), ==, 0);
3849
 
  g_assert_cmpint(rmdir(tempdir), ==, 0);
3850
 
}
3851
 
 
3852
 
static
3853
3673
void test_add_inotify_dir_watch_IN_DELETE(__attribute__((unused))
3854
3674
                                          test_fixture *fixture,
3855
3675
                                          __attribute__((unused))
3913
3733
  g_assert_cmpint(rmdir(tempdir), ==, 0);
3914
3734
}
3915
3735
 
3916
 
static
3917
 
void test_add_inotify_dir_watch_IN_EXCL_UNLINK(__attribute__((unused))
3918
 
                                               test_fixture *fixture,
3919
 
                                               __attribute__((unused))
3920
 
                                               gconstpointer
3921
 
                                               user_data){
3922
 
  __attribute__((cleanup(cleanup_close)))
3923
 
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3924
 
  g_assert_cmpint(epoll_fd, >=, 0);
3925
 
  __attribute__((cleanup(cleanup_queue)))
3926
 
    task_queue *queue = create_queue();
3927
 
  g_assert_nonnull(queue);
3928
 
  __attribute__((cleanup(string_set_clear)))
3929
 
    string_set cancelled_filenames = {};
3930
 
  const mono_microsecs current_time = 0;
3931
 
 
3932
 
  bool quit_now = false;
3933
 
  buffer password = {};
3934
 
  bool mandos_client_exited = false;
3935
 
  bool password_is_read = false;
3936
 
 
3937
 
  __attribute__((cleanup(cleanup_string)))
3938
 
    char *tempdir = make_temporary_directory();
3939
 
  g_assert_nonnull(tempdir);
3940
 
 
3941
 
  __attribute__((cleanup(cleanup_string)))
3942
 
    char *tempfile = make_temporary_file_in_directory(tempdir);
3943
 
  g_assert_nonnull(tempfile);
3944
 
  int tempfile_fd = open(tempfile, O_WRONLY | O_CLOEXEC | O_NOCTTY
3945
 
                         | O_NOFOLLOW);
3946
 
  g_assert_cmpint(tempfile_fd, >, 2);
3947
 
 
3948
 
  g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
3949
 
                                      &password, tempdir,
3950
 
                                      &cancelled_filenames,
3951
 
                                      &current_time,
3952
 
                                      &mandos_client_exited,
3953
 
                                      &password_is_read));
3954
 
  g_assert_cmpint(unlink(tempfile), ==, 0);
3955
 
 
3956
 
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
3957
 
 
3958
 
  const task_context *const added_read_task
3959
 
    = find_matching_task(queue,
3960
 
                         (task_context){ .func=read_inotify_event });
3961
 
  g_assert_nonnull(added_read_task);
3962
 
 
3963
 
  g_assert_cmpint(added_read_task->fd, >, 2);
3964
 
  g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd));
3965
 
 
3966
 
  /* "sufficient to read at least one event." - inotify(7) */
3967
 
  const size_t ievent_size = (sizeof(struct inotify_event)
3968
 
                              + NAME_MAX + 1);
3969
 
  struct inotify_event *ievent = malloc(ievent_size);
3970
 
  g_assert_nonnull(ievent);
3971
 
 
3972
 
  ssize_t read_size = 0;
3973
 
  read_size = read(added_read_task->fd, ievent, ievent_size);
3974
 
 
3975
 
  g_assert_cmpint((int)read_size, >, 0);
3976
 
  g_assert_true(ievent->mask & IN_DELETE);
3977
 
  g_assert_cmpstr(ievent->name, ==, basename(tempfile));
3978
 
 
3979
 
  g_assert_cmpint(close(tempfile_fd), ==, 0);
3980
 
 
3981
 
  /* IN_EXCL_UNLINK should make the closing of the previously unlinked
3982
 
     file not appear as an ievent, so we should not see it now. */
3983
 
  read_size = read(added_read_task->fd, ievent, ievent_size);
3984
 
  g_assert_cmpint((int)read_size, ==, -1);
3985
 
  g_assert_true(errno == EAGAIN);
3986
 
 
3987
 
  free(ievent);
3988
 
 
3989
 
  g_assert_cmpint(rmdir(tempdir), ==, 0);
3990
 
}
3991
 
 
3992
3736
static void test_read_inotify_event_readerror(__attribute__((unused))
3993
3737
                                              test_fixture *fixture,
3994
3738
                                              __attribute__((unused))
4000
3744
  const mono_microsecs current_time = 0;
4001
3745
 
4002
3746
  /* Reading /proc/self/mem from offset 0 will always result in EIO */
4003
 
  const int fd = open("/proc/self/mem",
4004
 
                      O_RDONLY | O_CLOEXEC | O_NOCTTY);
 
3747
  const int fd = open("/proc/self/mem", O_RDONLY | O_CLOEXEC);
4005
3748
 
4006
3749
  bool quit_now = false;
4007
3750
  __attribute__((cleanup(cleanup_queue)))
4186
3929
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4187
3930
                                  + NAME_MAX + 1);
4188
3931
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
4189
 
  struct {
4190
 
    struct inotify_event event;
4191
 
    char name_buffer[NAME_MAX + 1];
4192
 
  } ievent_buffer;
4193
 
  struct inotify_event *const ievent = &ievent_buffer.event;
 
3932
  char ievent_buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
 
3933
  struct inotify_event *ievent = ((struct inotify_event *)
 
3934
                                  ievent_buffer);
4194
3935
 
4195
3936
  const char dummy_file_name[] = "ask.dummy_file_name";
4196
3937
  ievent->mask = IN_CLOSE_WRITE;
4198
3939
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4199
3940
  const size_t ievent_size = (sizeof(struct inotify_event)
4200
3941
                              + sizeof(dummy_file_name));
4201
 
#if defined(__GNUC__) and __GNUC__ >= 11
4202
 
#pragma GCC diagnostic push
4203
 
  /* ievent is pointing into a struct which is of sufficient size */
4204
 
#pragma GCC diagnostic ignored "-Wstringop-overread"
4205
 
#endif
4206
 
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
 
3942
  g_assert_cmpint(write(pipefds[1], ievent_buffer, ievent_size),
4207
3943
                  ==, ievent_size);
4208
 
#if defined(__GNUC__) and __GNUC__ >= 11
4209
 
#pragma GCC diagnostic pop
4210
 
#endif
4211
3944
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4212
3945
 
4213
3946
  bool quit_now = false;
4289
4022
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4290
4023
                                  + NAME_MAX + 1);
4291
4024
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
4292
 
  struct {
4293
 
    struct inotify_event event;
4294
 
    char name_buffer[NAME_MAX + 1];
4295
 
  } ievent_buffer;
4296
 
  struct inotify_event *const ievent = &ievent_buffer.event;
 
4025
  char ievent_buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
 
4026
  struct inotify_event *ievent = ((struct inotify_event *)
 
4027
                                  ievent_buffer);
4297
4028
 
4298
4029
  const char dummy_file_name[] = "ask.dummy_file_name";
4299
4030
  ievent->mask = IN_MOVED_TO;
4301
4032
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4302
4033
  const size_t ievent_size = (sizeof(struct inotify_event)
4303
4034
                              + sizeof(dummy_file_name));
4304
 
#if defined(__GNUC__) and __GNUC__ >= 11
4305
 
#pragma GCC diagnostic push
4306
 
  /* ievent is pointing into a struct which is of sufficient size */
4307
 
#pragma GCC diagnostic ignored "-Wstringop-overread"
4308
 
#endif
4309
 
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
 
4035
  g_assert_cmpint(write(pipefds[1], ievent_buffer, ievent_size),
4310
4036
                  ==, ievent_size);
4311
 
#if defined(__GNUC__) and __GNUC__ >= 11
4312
 
#pragma GCC diagnostic pop
4313
 
#endif
4314
4037
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4315
4038
 
4316
4039
  bool quit_now = false;
4375
4098
      }));
4376
4099
}
4377
4100
 
4378
 
static
4379
 
void test_read_inotify_event_IN_MOVED_FROM(__attribute__((unused))
4380
 
                                           test_fixture *fixture,
4381
 
                                           __attribute__((unused))
4382
 
                                           gconstpointer user_data){
4383
 
  __attribute__((cleanup(cleanup_close)))
4384
 
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
4385
 
  g_assert_cmpint(epoll_fd, >=, 0);
4386
 
  __attribute__((cleanup(string_set_clear)))
4387
 
    string_set cancelled_filenames = {};
4388
 
  const mono_microsecs current_time = 0;
4389
 
 
4390
 
  int pipefds[2];
4391
 
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
4392
 
 
4393
 
  /* "sufficient to read at least one event." - inotify(7) */
4394
 
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4395
 
                                  + NAME_MAX + 1);
4396
 
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
4397
 
  struct {
4398
 
    struct inotify_event event;
4399
 
    char name_buffer[NAME_MAX + 1];
4400
 
  } ievent_buffer;
4401
 
  struct inotify_event *const ievent = &ievent_buffer.event;
4402
 
 
4403
 
  const char dummy_file_name[] = "ask.dummy_file_name";
4404
 
  ievent->mask = IN_MOVED_FROM;
4405
 
  ievent->len = sizeof(dummy_file_name);
4406
 
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4407
 
  const size_t ievent_size = (sizeof(struct inotify_event)
4408
 
                              + sizeof(dummy_file_name));
4409
 
#if defined(__GNUC__) and __GNUC__ >= 11
4410
 
#pragma GCC diagnostic push
4411
 
  /* ievent is pointing into a struct which is of sufficient size */
4412
 
#pragma GCC diagnostic ignored "-Wstringop-overread"
4413
 
#endif
4414
 
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
4415
 
                  ==, ievent_size);
4416
 
#if defined(__GNUC__) and __GNUC__ >= 11
4417
 
#pragma GCC diagnostic pop
4418
 
#endif
4419
 
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4420
 
 
4421
 
  bool quit_now = false;
4422
 
  buffer password = {};
4423
 
  bool mandos_client_exited = false;
4424
 
  bool password_is_read = false;
4425
 
  __attribute__((cleanup(cleanup_queue)))
4426
 
    task_queue *queue = create_queue();
4427
 
  g_assert_nonnull(queue);
4428
 
 
4429
 
  task_context task = {
4430
 
    .func=read_inotify_event,
4431
 
    .epoll_fd=epoll_fd,
4432
 
    .fd=pipefds[0],
4433
 
    .quit_now=&quit_now,
4434
 
    .password=&password,
4435
 
    .filename=strdup("/nonexistent"),
4436
 
    .cancelled_filenames=&cancelled_filenames,
4437
 
    .current_time=&current_time,
4438
 
    .mandos_client_exited=&mandos_client_exited,
4439
 
    .password_is_read=&password_is_read,
4440
 
  };
4441
 
  task.func(task, queue);
4442
 
  g_assert_false(quit_now);
4443
 
  g_assert_true(queue->next_run == 0);
4444
 
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
4445
 
 
4446
 
  g_assert_nonnull(find_matching_task(queue, (task_context){
4447
 
        .func=read_inotify_event,
4448
 
        .epoll_fd=epoll_fd,
4449
 
        .fd=pipefds[0],
4450
 
        .quit_now=&quit_now,
4451
 
        .password=&password,
4452
 
        .filename=task.filename,
4453
 
        .cancelled_filenames=&cancelled_filenames,
4454
 
        .current_time=&current_time,
4455
 
        .mandos_client_exited=&mandos_client_exited,
4456
 
        .password_is_read=&password_is_read,
4457
 
      }));
4458
 
 
4459
 
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
4460
 
                                   EPOLLIN | EPOLLRDHUP));
4461
 
 
4462
 
  __attribute__((cleanup(cleanup_string)))
4463
 
    char *filename = NULL;
4464
 
  g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename,
4465
 
                           dummy_file_name), >, 0);
4466
 
  g_assert_nonnull(filename);
4467
 
  g_assert_true(string_set_contains(*task.cancelled_filenames,
4468
 
                                    filename));
4469
 
}
4470
 
 
4471
4101
static void test_read_inotify_event_IN_DELETE(__attribute__((unused))
4472
4102
                                              test_fixture *fixture,
4473
4103
                                              __attribute__((unused))
4487
4117
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4488
4118
                                  + NAME_MAX + 1);
4489
4119
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
4490
 
  struct {
4491
 
    struct inotify_event event;
4492
 
    char name_buffer[NAME_MAX + 1];
4493
 
  } ievent_buffer;
4494
 
  struct inotify_event *const ievent = &ievent_buffer.event;
 
4120
  char ievent_buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
 
4121
  struct inotify_event *ievent = ((struct inotify_event *)
 
4122
                                  ievent_buffer);
4495
4123
 
4496
4124
  const char dummy_file_name[] = "ask.dummy_file_name";
4497
4125
  ievent->mask = IN_DELETE;
4499
4127
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4500
4128
  const size_t ievent_size = (sizeof(struct inotify_event)
4501
4129
                              + sizeof(dummy_file_name));
4502
 
#if defined(__GNUC__) and __GNUC__ >= 11
4503
 
#pragma GCC diagnostic push
4504
 
  /* ievent is pointing into a struct which is of sufficient size */
4505
 
#pragma GCC diagnostic ignored "-Wstringop-overread"
4506
 
#endif
4507
 
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
 
4130
  g_assert_cmpint(write(pipefds[1], ievent_buffer, ievent_size),
4508
4131
                  ==, ievent_size);
4509
 
#if defined(__GNUC__) and __GNUC__ >= 11
4510
 
#pragma GCC diagnostic pop
4511
 
#endif
4512
4132
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4513
4133
 
4514
4134
  bool quit_now = false;
4579
4199
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4580
4200
                                  + NAME_MAX + 1);
4581
4201
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
4582
 
  struct {
4583
 
    struct inotify_event event;
4584
 
    char name_buffer[NAME_MAX + 1];
4585
 
  } ievent_buffer;
4586
 
  struct inotify_event *const ievent = &ievent_buffer.event;
 
4202
  char ievent_buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
 
4203
  struct inotify_event *ievent = ((struct inotify_event *)
 
4204
                                  ievent_buffer);
4587
4205
 
4588
4206
  const char dummy_file_name[] = "ignored.dummy_file_name";
4589
4207
  ievent->mask = IN_CLOSE_WRITE;
4591
4209
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4592
4210
  const size_t ievent_size = (sizeof(struct inotify_event)
4593
4211
                              + sizeof(dummy_file_name));
4594
 
#if defined(__GNUC__) and __GNUC__ >= 11
4595
 
#pragma GCC diagnostic push
4596
 
  /* ievent is pointing into a struct which is of sufficient size */
4597
 
#pragma GCC diagnostic ignored "-Wstringop-overread"
4598
 
#endif
4599
 
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
 
4212
  g_assert_cmpint(write(pipefds[1], ievent_buffer, ievent_size),
4600
4213
                  ==, ievent_size);
4601
 
#if defined(__GNUC__) and __GNUC__ >= 11
4602
 
#pragma GCC diagnostic pop
4603
 
#endif
4604
4214
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4605
4215
 
4606
4216
  bool quit_now = false;
4663
4273
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4664
4274
                                  + NAME_MAX + 1);
4665
4275
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
4666
 
  struct {
4667
 
    struct inotify_event event;
4668
 
    char name_buffer[NAME_MAX + 1];
4669
 
  } ievent_buffer;
4670
 
  struct inotify_event *const ievent = &ievent_buffer.event;
 
4276
  char ievent_buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
 
4277
  struct inotify_event *ievent = ((struct inotify_event *)
 
4278
                                  ievent_buffer);
4671
4279
 
4672
4280
  const char dummy_file_name[] = "ignored.dummy_file_name";
4673
4281
  ievent->mask = IN_MOVED_TO;
4675
4283
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4676
4284
  const size_t ievent_size = (sizeof(struct inotify_event)
4677
4285
                              + sizeof(dummy_file_name));
4678
 
#if defined(__GNUC__) and __GNUC__ >= 11
4679
 
#pragma GCC diagnostic push
4680
 
  /* ievent is pointing into a struct which is of sufficient size */
4681
 
#pragma GCC diagnostic ignored "-Wstringop-overread"
4682
 
#endif
4683
 
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
 
4286
  g_assert_cmpint(write(pipefds[1], ievent_buffer, ievent_size),
4684
4287
                  ==, ievent_size);
4685
 
#if defined(__GNUC__) and __GNUC__ >= 11
4686
 
#pragma GCC diagnostic pop
4687
 
#endif
4688
4288
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4689
4289
 
4690
4290
  bool quit_now = false;
4730
4330
                                   EPOLLIN | EPOLLRDHUP));
4731
4331
}
4732
4332
 
4733
 
static void
4734
 
test_read_inotify_event_IN_MOVED_FROM_badname(__attribute__((unused))
4735
 
                                              test_fixture *fixture,
4736
 
                                              __attribute__((unused))
4737
 
                                              gconstpointer
4738
 
                                              user_data){
4739
 
  __attribute__((cleanup(cleanup_close)))
4740
 
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
4741
 
  g_assert_cmpint(epoll_fd, >=, 0);
4742
 
  __attribute__((cleanup(string_set_clear)))
4743
 
    string_set cancelled_filenames = {};
4744
 
  const mono_microsecs current_time = 0;
4745
 
 
4746
 
  int pipefds[2];
4747
 
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
4748
 
 
4749
 
  /* "sufficient to read at least one event." - inotify(7) */
4750
 
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4751
 
                                  + NAME_MAX + 1);
4752
 
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
4753
 
  struct {
4754
 
    struct inotify_event event;
4755
 
    char name_buffer[NAME_MAX + 1];
4756
 
  } ievent_buffer;
4757
 
  struct inotify_event *const ievent = &ievent_buffer.event;
4758
 
 
4759
 
  const char dummy_file_name[] = "ignored.dummy_file_name";
4760
 
  ievent->mask = IN_MOVED_FROM;
4761
 
  ievent->len = sizeof(dummy_file_name);
4762
 
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4763
 
  const size_t ievent_size = (sizeof(struct inotify_event)
4764
 
                              + sizeof(dummy_file_name));
4765
 
#if defined(__GNUC__) and __GNUC__ >= 11
4766
 
#pragma GCC diagnostic push
4767
 
  /* ievent is pointing into a struct which is of sufficient size */
4768
 
#pragma GCC diagnostic ignored "-Wstringop-overread"
4769
 
#endif
4770
 
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
4771
 
                  ==, ievent_size);
4772
 
#if defined(__GNUC__) and __GNUC__ >= 11
4773
 
#pragma GCC diagnostic pop
4774
 
#endif
4775
 
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4776
 
 
4777
 
  bool quit_now = false;
4778
 
  buffer password = {};
4779
 
  bool mandos_client_exited = false;
4780
 
  bool password_is_read = false;
4781
 
  __attribute__((cleanup(cleanup_queue)))
4782
 
    task_queue *queue = create_queue();
4783
 
  g_assert_nonnull(queue);
4784
 
 
4785
 
  task_context task = {
4786
 
    .func=read_inotify_event,
4787
 
    .epoll_fd=epoll_fd,
4788
 
    .fd=pipefds[0],
4789
 
    .quit_now=&quit_now,
4790
 
    .password=&password,
4791
 
    .filename=strdup("/nonexistent"),
4792
 
    .cancelled_filenames=&cancelled_filenames,
4793
 
    .current_time=&current_time,
4794
 
    .mandos_client_exited=&mandos_client_exited,
4795
 
    .password_is_read=&password_is_read,
4796
 
  };
4797
 
  task.func(task, queue);
4798
 
  g_assert_false(quit_now);
4799
 
  g_assert_true(queue->next_run == 0);
4800
 
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
4801
 
 
4802
 
  g_assert_nonnull(find_matching_task(queue, (task_context){
4803
 
        .func=read_inotify_event,
4804
 
        .epoll_fd=epoll_fd,
4805
 
        .fd=pipefds[0],
4806
 
        .quit_now=&quit_now,
4807
 
        .password=&password,
4808
 
        .filename=task.filename,
4809
 
        .cancelled_filenames=&cancelled_filenames,
4810
 
        .current_time=&current_time,
4811
 
        .mandos_client_exited=&mandos_client_exited,
4812
 
        .password_is_read=&password_is_read,
4813
 
      }));
4814
 
 
4815
 
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
4816
 
                                   EPOLLIN | EPOLLRDHUP));
4817
 
 
4818
 
  __attribute__((cleanup(cleanup_string)))
4819
 
    char *filename = NULL;
4820
 
  g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename,
4821
 
                           dummy_file_name), >, 0);
4822
 
  g_assert_nonnull(filename);
4823
 
  g_assert_false(string_set_contains(cancelled_filenames, filename));
4824
 
}
4825
 
 
4826
4333
static
4827
4334
void test_read_inotify_event_IN_DELETE_badname(__attribute__((unused))
4828
4335
                                               test_fixture *fixture,
4843
4350
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4844
4351
                                  + NAME_MAX + 1);
4845
4352
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
4846
 
  struct {
4847
 
    struct inotify_event event;
4848
 
    char name_buffer[NAME_MAX + 1];
4849
 
  } ievent_buffer;
4850
 
  struct inotify_event *const ievent = &ievent_buffer.event;
 
4353
  char ievent_buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
 
4354
  struct inotify_event *ievent = ((struct inotify_event *)
 
4355
                                  ievent_buffer);
4851
4356
 
4852
4357
  const char dummy_file_name[] = "ignored.dummy_file_name";
4853
4358
  ievent->mask = IN_DELETE;
4855
4360
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4856
4361
  const size_t ievent_size = (sizeof(struct inotify_event)
4857
4362
                              + sizeof(dummy_file_name));
4858
 
#if defined(__GNUC__) and __GNUC__ >= 11
4859
 
#pragma GCC diagnostic push
4860
 
  /* ievent is pointing into a struct which is of sufficient size */
4861
 
#pragma GCC diagnostic ignored "-Wstringop-overread"
4862
 
#endif
4863
 
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
 
4363
  g_assert_cmpint(write(pipefds[1], ievent_buffer, ievent_size),
4864
4364
                  ==, ievent_size);
4865
 
#if defined(__GNUC__) and __GNUC__ >= 11
4866
 
#pragma GCC diagnostic pop
4867
 
#endif
4868
4365
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4869
4366
 
4870
4367
  bool quit_now = false;
5756
5253
                                            __attribute__((unused))
5757
5254
                                            gconstpointer user_data){
5758
5255
  __attribute__((cleanup(cleanup_close)))
5759
 
    const int epoll_fd = open("/dev/null",
5760
 
                              O_WRONLY | O_CLOEXEC | O_NOCTTY);
 
5256
    const int epoll_fd = open("/dev/null", O_WRONLY | O_CLOEXEC);
5761
5257
  __attribute__((cleanup(cleanup_string)))
5762
5258
    char *const question_filename = strdup("/nonexistent/question");
5763
5259
  g_assert_nonnull(question_filename);
5892
5388
  char write_data[PIPE_BUF];
5893
5389
  {
5894
5390
    /* Construct test password buffer */
5895
 
    /* Start with + since that is what the real protocol uses */
 
5391
    /* Start with + since that is what the real procotol uses */
5896
5392
    write_data[0] = '+';
5897
5393
    /* Set a special character at string end just to mark the end */
5898
5394
    write_data[sizeof(write_data)-2] = 'y';
6050
5546
  char *const filename = strdup("/nonexistent/socket");
6051
5547
  __attribute__((cleanup(string_set_clear)))
6052
5548
    string_set cancelled_filenames = {};
6053
 
  int socketfds[2];
6054
 
 
6055
 
  /* Find a message size which triggers EMSGSIZE */
6056
 
  __attribute__((cleanup(cleanup_string)))
6057
 
    char *message_buffer = NULL;
6058
 
  size_t message_size = PIPE_BUF + 1;
6059
 
  for(ssize_t ssret = 0; ssret >= 0; message_size += 1024){
6060
 
    if(message_size >= 1024*1024*1024){ /* 1 GiB */
6061
 
      g_test_skip("Skipping EMSGSIZE test: Will not try 1GiB");
6062
 
      return;
6063
 
    }
6064
 
    message_buffer = realloc(message_buffer, message_size);
6065
 
    if(message_buffer == NULL){
6066
 
      g_test_skip("Skipping EMSGSIZE test");
6067
 
      g_test_message("Failed to malloc() %" PRIuMAX " bytes",
6068
 
                     (uintmax_t)message_size);
6069
 
      return;
6070
 
    }
6071
 
    /* Fill buffer with 'x' */
6072
 
    memset(message_buffer, 'x', message_size);
6073
 
    /* Create a new socketpair for each message size to avoid having
6074
 
       to empty the pipe by reading the message to a separate buffer
6075
 
    */
6076
 
    g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM
6077
 
                               | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
6078
 
                               socketfds), ==, 0);
6079
 
    ssret = send(socketfds[1], message_buffer, message_size,
6080
 
                 MSG_NOSIGNAL);
6081
 
    error_t saved_errno = errno;
6082
 
    g_assert_cmpint(close(socketfds[0]), ==, 0);
6083
 
    g_assert_cmpint(close(socketfds[1]), ==, 0);
6084
 
 
6085
 
    if(ssret < 0){
6086
 
      if(saved_errno != EMSGSIZE) {
6087
 
        g_test_skip("Skipping EMSGSIZE test");
6088
 
        g_test_message("Error on send(%" PRIuMAX " bytes): %s",
6089
 
                       (uintmax_t)message_size,
6090
 
                       strerror(saved_errno));
6091
 
        return;
6092
 
      }
6093
 
      break;
6094
 
    } else if(ssret != (ssize_t)message_size){
6095
 
      g_test_skip("Skipping EMSGSIZE test");
6096
 
      g_test_message("Partial send(): %" PRIuMAX " of %" PRIdMAX
6097
 
                     " bytes", (uintmax_t)ssret,
6098
 
                     (intmax_t)message_size);
6099
 
      return;
6100
 
    }
6101
 
  }
6102
 
  g_test_message("EMSGSIZE triggered by %" PRIdMAX " bytes",
6103
 
                 (intmax_t)message_size);
6104
 
 
6105
 
  buffer password = {
6106
 
    .data=message_buffer,
6107
 
    .length=message_size - 2,   /* Compensate for added '+' and NUL */
6108
 
    .allocated=message_size,
 
5549
  const size_t oversized = 1024*1024; /* Limit seems to be 212960 */
 
5550
  __attribute__((cleanup(cleanup_buffer)))
 
5551
    buffer password = {
 
5552
    .data=malloc(oversized),
 
5553
    .length=oversized,
 
5554
    .allocated=oversized,
6109
5555
  };
 
5556
  g_assert_nonnull(password.data);
6110
5557
  if(mlock(password.data, password.allocated) != 0){
6111
5558
    g_assert_true(errno == EPERM or errno == ENOMEM);
6112
5559
  }
 
5560
  /* Construct test password buffer */
 
5561
  /* Start with + since that is what the real procotol uses */
 
5562
  password.data[0] = '+';
 
5563
  /* Set a special character at string end just to mark the end */
 
5564
  password.data[oversized-3] = 'y';
 
5565
  /* Set NUL at buffer end, as suggested by the protocol */
 
5566
  password.data[oversized-2] = '\0';
 
5567
  /* Fill rest of password with 'x' */
 
5568
  memset(password.data+1, 'x', oversized-3);
6113
5569
 
6114
5570
  __attribute__((cleanup(cleanup_queue)))
6115
5571
    task_queue *queue = create_queue();
6116
5572
  g_assert_nonnull(queue);
 
5573
  int socketfds[2];
6117
5574
  g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM
6118
5575
                             | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
6119
5576
                             socketfds), ==, 0);
6206
5663
                                            __attribute__((unused))
6207
5664
                                            gconstpointer user_data){
6208
5665
  __attribute__((cleanup(cleanup_close)))
6209
 
    const int epoll_fd = open("/dev/null",
6210
 
                              O_WRONLY | O_CLOEXEC | O_NOCTTY);
 
5666
    const int epoll_fd = open("/dev/null", O_WRONLY | O_CLOEXEC);
6211
5667
  __attribute__((cleanup(cleanup_string)))
6212
5668
    char *const question_filename = strdup("/nonexistent/question");
6213
5669
  g_assert_nonnull(question_filename);
6476
5932
                                              const char *const
6477
5933
                                              dirname){
6478
5934
  __attribute__((cleanup(cleanup_close)))
6479
 
    const int devnull_fd = open("/dev/null",
6480
 
                                O_WRONLY | O_CLOEXEC | O_NOCTTY);
 
5935
    const int devnull_fd = open("/dev/null", O_WRONLY | O_CLOEXEC);
6481
5936
  g_assert_cmpint(devnull_fd, >=, 0);
6482
5937
  __attribute__((cleanup(cleanup_close)))
6483
5938
    const int real_stderr_fd = dup(STDERR_FILENO);
8026
7481
  test_add("/parse_arguments/mixed", test_parse_arguments_mixed);
8027
7482
  test_add("/queue/create", test_create_queue);
8028
7483
  test_add("/queue/add", test_add_to_queue);
8029
 
  test_add("/queue/add/overflow", test_add_to_queue_overflow);
8030
7484
  test_add("/queue/has_question/empty",
8031
7485
           test_queue_has_question_empty);
8032
7486
  test_add("/queue/has_question/false",
8119
7573
              test_add_inotify_dir_watch);
8120
7574
  test_add_st("/task-creators/add_inotify_dir_watch/fail",
8121
7575
              test_add_inotify_dir_watch_fail);
8122
 
  test_add_st("/task-creators/add_inotify_dir_watch/not-a-directory",
8123
 
              test_add_inotify_dir_watch_nondir);
8124
7576
  test_add_st("/task-creators/add_inotify_dir_watch/EAGAIN",
8125
7577
              test_add_inotify_dir_watch_EAGAIN);
8126
7578
  test_add_st("/task-creators/add_inotify_dir_watch/IN_CLOSE_WRITE",
8127
7579
              test_add_inotify_dir_watch_IN_CLOSE_WRITE);
8128
7580
  test_add_st("/task-creators/add_inotify_dir_watch/IN_MOVED_TO",
8129
7581
              test_add_inotify_dir_watch_IN_MOVED_TO);
8130
 
  test_add_st("/task-creators/add_inotify_dir_watch/IN_MOVED_FROM",
8131
 
              test_add_inotify_dir_watch_IN_MOVED_FROM);
8132
 
  test_add_st("/task-creators/add_inotify_dir_watch/IN_EXCL_UNLINK",
8133
 
              test_add_inotify_dir_watch_IN_EXCL_UNLINK);
8134
7582
  test_add_st("/task-creators/add_inotify_dir_watch/IN_DELETE",
8135
7583
              test_add_inotify_dir_watch_IN_DELETE);
8136
7584
  test_add_st("/task/read_inotify_event/readerror",
8145
7593
              test_read_inotify_event_IN_CLOSE_WRITE);
8146
7594
  test_add_st("/task/read_inotify_event/IN_MOVED_TO",
8147
7595
              test_read_inotify_event_IN_MOVED_TO);
8148
 
  test_add_st("/task/read_inotify_event/IN_MOVED_FROM",
8149
 
              test_read_inotify_event_IN_MOVED_FROM);
8150
7596
  test_add_st("/task/read_inotify_event/IN_DELETE",
8151
7597
              test_read_inotify_event_IN_DELETE);
8152
7598
  test_add_st("/task/read_inotify_event/IN_CLOSE_WRITE/badname",
8153
7599
              test_read_inotify_event_IN_CLOSE_WRITE_badname);
8154
7600
  test_add_st("/task/read_inotify_event/IN_MOVED_TO/badname",
8155
7601
              test_read_inotify_event_IN_MOVED_TO_badname);
8156
 
  test_add_st("/task/read_inotify_event/IN_MOVED_FROM/badname",
8157
 
              test_read_inotify_event_IN_MOVED_FROM_badname);
8158
7602
  test_add_st("/task/read_inotify_event/IN_DELETE/badname",
8159
7603
              test_read_inotify_event_IN_DELETE_badname);
8160
7604
  test_add_st("/task/open_and_parse_question/ENOENT",
8257
7701
  g_option_context_set_help_enabled(context, FALSE);
8258
7702
  g_option_context_set_ignore_unknown_options(context, TRUE);
8259
7703
 
8260
 
  gboolean should_run_tests = FALSE;
 
7704
  gboolean run_tests = FALSE;
8261
7705
  GOptionEntry entries[] = {
8262
7706
    { "test", 0, 0, G_OPTION_ARG_NONE,
8263
 
      &should_run_tests, "Run tests", NULL },
 
7707
      &run_tests, "Run tests", NULL },
8264
7708
    { NULL }
8265
7709
  };
8266
7710
  g_option_context_add_main_entries(context, entries, NULL);
8273
7717
  }
8274
7718
 
8275
7719
  g_option_context_free(context);
8276
 
  return should_run_tests != FALSE;
 
7720
  return run_tests != FALSE;
8277
7721
}
8278
 
 
8279
 
/*
8280
 
Local Variables:
8281
 
run-tests:
8282
 
(lambda ()
8283
 
  (if (not (funcall run-tests-in-test-buffer default-directory))
8284
 
      (funcall show-test-buffer-in-test-window)
8285
 
    (funcall remove-test-window)))
8286
 
run-tests-in-test-buffer:
8287
 
(lambda (dir)
8288
 
  (with-current-buffer (get-buffer-create "*Test*")
8289
 
    (setq buffer-read-only nil
8290
 
          default-directory dir)
8291
 
    (erase-buffer)
8292
 
    (compilation-mode))
8293
 
  (let ((process-result
8294
 
         (let ((inhibit-read-only t))
8295
 
           (process-file-shell-command
8296
 
            (funcall get-command-line) nil "*Test*"))))
8297
 
    (and (numberp process-result)
8298
 
         (= process-result 0))))
8299
 
get-command-line:
8300
 
(lambda ()
8301
 
  (let*
8302
 
      ((build-directory
8303
 
        (funcall find-build-directory (buffer-file-name)))
8304
 
       (local-build-directory
8305
 
        (if (fboundp 'file-local-name)
8306
 
            (file-local-name build-directory)
8307
 
          (or (file-remote-p build-directory 'localname)
8308
 
              build-directory)))
8309
 
       (command
8310
 
        (file-relative-name (file-name-sans-extension
8311
 
                             (buffer-file-name)) build-directory))
8312
 
       (qbdir (shell-quote-argument local-build-directory))
8313
 
       (qcmd (shell-quote-argument command)))
8314
 
    (format (concat "cd %s && CFLAGS=-Werror make --silent %s"
8315
 
             " && %s --test --verbose") qbdir qcmd qcmd)))
8316
 
find-build-directory:
8317
 
(lambda (try-directory &optional base-directory)
8318
 
  (let ((base-directory (or base-directory try-directory)))
8319
 
    (cond ((equal try-directory "/") base-directory)
8320
 
          ((file-readable-p
8321
 
            (concat (file-name-as-directory try-directory)
8322
 
                    "Makefile")) try-directory)
8323
 
          ((funcall find-build-directory
8324
 
                    (directory-file-name (file-name-directory
8325
 
                                          try-directory))
8326
 
                    base-directory)))))
8327
 
show-test-buffer-in-test-window:
8328
 
(lambda ()
8329
 
  (when (not (get-buffer-window-list "*Test*"))
8330
 
    (setq next-error-last-buffer (get-buffer "*Test*"))
8331
 
    (let* ((side (if (>= (window-width) 146) 'right 'bottom))
8332
 
           (display-buffer-overriding-action
8333
 
            `((display-buffer-in-side-window) (side . ,side)
8334
 
              (window-height . fit-window-to-buffer)
8335
 
              (window-width . fit-window-to-buffer))))
8336
 
      (display-buffer "*Test*"))))
8337
 
remove-test-window:
8338
 
(lambda ()
8339
 
  (let ((test-window (get-buffer-window "*Test*")))
8340
 
    (if test-window (delete-window test-window))))
8341
 
eval: (add-hook 'after-save-hook run-tests 90 t)
8342
 
End:
8343
 
*/