/mandos/trunk

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

« back to all changes in this revision

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

  • Committer: Teddy Hogeborn
  • Date: 2024-09-09 04:24:39 UTC
  • Revision ID: teddy@recompile.se-20240909042439-j85mr20uli2hnyis
Eliminate compiler warnings

Many programs use nested functions, which now result in a linker
warning about executable stack.  Hide this warning.  Also, rewrite a
loop in the plymouth plugin to avoid warning about signed overflow.
This change also makes the plugin pick the alphabetically first
process entry instead of the last, in case many plymouth processes are
found (which should be unlikely).

* Makefile (plugin-runner, dracut-module/password-agent,
  plugins.d/password-prompt, plugins.d/mandos-client,
  plugins.d/plymouth): New target; set LDFLAGS to add "-Xlinker
  --no-warn-execstack".
* plugins.d/plymouth.c (get_pid): When no pid files are found, and we
  are looking through the process list, go though it from the start
  instead of from the end, i.e. in normal alphabetical order and not
  in reverse order.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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)))))); -*- */
 
1
/* -*- coding: utf-8; lexical-binding: t -*- */
2
2
/*
3
3
 * Mandos password agent - Simple password agent to run Mandos client
4
4
 *
5
 
 * Copyright © 2019 Teddy Hogeborn
6
 
 * Copyright © 2019 Björn Påhlsson
 
5
 * Copyright © 2019-2022 Teddy Hogeborn
 
6
 * Copyright © 2019-2022 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
27
 
#include <inttypes.h>           /* uintmax_t, PRIuMAX, PRIdMAX,
28
 
                                   intmax_t, uint32_t, SCNx32,
29
 
                                   SCNuMAX, SCNxMAX */
30
 
#include <stddef.h>             /* size_t */
 
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 */
31
33
#include <sys/types.h>          /* pid_t, uid_t, gid_t, getuid(),
32
34
                                   getpid() */
33
35
#include <stdbool.h>            /* bool, true, false */
40
42
                                   NSIG, sigismember(), SA_ONSTACK,
41
43
                                   SIG_DFL, SIG_IGN, SIGINT, SIGQUIT,
42
44
                                   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() */
43
52
#include <stdlib.h>             /* EXIT_SUCCESS, EXIT_FAILURE,
44
 
                                   malloc(), free(), strtoumax(),
45
 
                                   realloc(), setenv(), calloc(),
46
 
                                   mkdtemp(), mkostemp() */
 
53
                                   malloc(), free(), realloc(),
 
54
                                   setenv(), calloc(), mkdtemp(),
 
55
                                   mkostemp() */
47
56
#include <iso646.h>             /* not, or, and, xor */
48
57
#include <error.h>              /* error() */
49
58
#include <sysexits.h>           /* EX_USAGE, EX_OSERR, EX_OSFILE */
50
59
#include <errno.h>              /* errno, error_t, EACCES,
51
 
                                   ENAMETOOLONG, ENOENT, EEXIST,
52
 
                                   ECHILD, EPERM, ENOMEM, EAGAIN,
53
 
                                   EINTR, ENOBUFS, EADDRINUSE,
 
60
                                   ENAMETOOLONG, ENOENT, ENOTDIR,
 
61
                                   ENOMEM, EEXIST, ECHILD, EPERM,
 
62
                                   EAGAIN, EINTR, ENOBUFS, EADDRINUSE,
54
63
                                   ECONNREFUSED, ECONNRESET,
55
64
                                   ETOOMANYREFS, EMSGSIZE, EBADF,
56
65
                                   EINVAL */
57
66
#include <string.h>             /* strdup(), memcpy(),
58
67
                                   explicit_bzero(), memset(),
59
68
                                   strcmp(), strlen(), strncpy(),
60
 
                                   memcmp(), basename() */
 
69
                                   memcmp(), basename(), strerror() */
61
70
#include <argz.h>               /* argz_create(), argz_count(),
62
71
                                   argz_extract(), argz_next(),
63
72
                                   argz_add() */
73
82
                                   ARGP_ERR_UNKNOWN, ARGP_KEY_ARGS,
74
83
                                   struct argp, argp_parse(),
75
84
                                   ARGP_NO_EXIT */
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() */
 
85
#include <stdint.h>             /* SIZE_MAX, uint32_t */
83
86
#include <sys/mman.h>           /* munlock(), mlock() */
84
87
#include <fcntl.h>              /* O_CLOEXEC, O_NONBLOCK, fcntl(),
85
88
                                   F_GETFD, F_GETFL, FD_CLOEXEC,
86
 
                                   open(), O_WRONLY, O_RDONLY */
 
89
                                   open(), O_WRONLY, O_NOCTTY,
 
90
                                   O_RDONLY, O_NOFOLLOW */
87
91
#include <sys/wait.h>           /* waitpid(), WNOHANG, WIFEXITED(),
88
92
                                   WEXITSTATUS() */
89
93
#include <limits.h>             /* PIPE_BUF, NAME_MAX, INT_MAX */
90
94
#include <sys/inotify.h>        /* inotify_init1(), IN_NONBLOCK,
91
95
                                   IN_CLOEXEC, inotify_add_watch(),
92
96
                                   IN_CLOSE_WRITE, IN_MOVED_TO,
93
 
                                   IN_DELETE, struct inotify_event */
 
97
                                   IN_MOVED_FROM, IN_DELETE,
 
98
                                   IN_EXCL_UNLINK, IN_ONLYDIR,
 
99
                                   struct inotify_event */
94
100
#include <fnmatch.h>            /* fnmatch(), FNM_FILE_NAME */
95
 
#include <stdio.h>              /* asprintf(), FILE, fopen(),
96
 
                                   getline(), sscanf(), feof(),
97
 
                                   ferror(), fclose(), stderr,
98
 
                                   rename(), fdopen(), fprintf(),
99
 
                                   fscanf() */
 
101
#include <stdio.h>              /* asprintf(), FILE, stderr, fopen(),
 
102
                                   fclose(), getline(), sscanf(),
 
103
                                   feof(), ferror(), rename(),
 
104
                                   fdopen(), fprintf(), fscanf() */
100
105
#include <glib.h>    /* GKeyFile, g_key_file_free(), g_key_file_new(),
101
106
                        GError, g_key_file_load_from_file(),
102
107
                        G_KEY_FILE_NONE, TRUE, G_FILE_ERROR_NOENT,
107
112
                        g_assert_null(), g_assert_false(),
108
113
                        g_assert_cmpint(), g_assert_cmpuint(),
109
114
                        g_test_skip(), g_assert_cmpstr(),
110
 
                        g_test_init(), g_test_add(), g_test_run(),
111
 
                        GOptionContext, g_option_context_new(),
 
115
                        g_test_message(), g_test_init(), g_test_add(),
 
116
                        g_test_run(), GOptionContext,
 
117
                        g_option_context_new(),
112
118
                        g_option_context_set_help_enabled(), FALSE,
113
119
                        g_option_context_set_ignore_unknown_options(),
114
120
                        gboolean, GOptionEntry, G_OPTION_ARG_NONE,
145
151
  mono_microsecs next_run;
146
152
} __attribute__((designated_init)) task_queue;
147
153
 
148
 
/* "func_type" - A function type for task functions
 
154
/* "task_func" - A function type for task functions
149
155
 
150
156
   I.e. functions for the code which runs when a task is run, all have
151
157
   this type */
431
437
    case EACCES:
432
438
    case ENAMETOOLONG:
433
439
    case ENOENT:
 
440
    case ENOTDIR:
434
441
      return EX_OSFILE;
435
442
    default:
436
443
      return EX_OSERR;
647
654
 
648
655
__attribute__((nonnull, warn_unused_result))
649
656
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
  }
650
664
  const size_t needed_size = sizeof(task_context)*(queue->length + 1);
651
665
  if(needed_size > (queue->allocated)){
652
666
    task_context *const new_tasks = realloc(queue->tasks,
857
871
  }
858
872
  close(pipefds[1]);
859
873
 
 
874
  if(pid == -1){
 
875
    error(0, errno, "Failed to fork()");
 
876
    close(pipefds[0]);
 
877
    return false;
 
878
  }
 
879
 
860
880
  if(not add_to_queue(queue, (task_context){
861
881
        .func=wait_for_mandos_client_exit,
862
882
        .pid=pid,
1017
1037
    return false;
1018
1038
  }
1019
1039
 
1020
 
  if(inotify_add_watch(fd, dir, IN_CLOSE_WRITE
1021
 
                       | IN_MOVED_TO | IN_DELETE)
 
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)
1022
1043
     == -1){
1023
1044
    error(0, errno, "Failed to create inotify watch on %s", dir);
1024
1045
    return false;
1071
1092
  /* "sufficient to read at least one event." - inotify(7) */
1072
1093
  const size_t ievent_size = (sizeof(struct inotify_event)
1073
1094
                              + NAME_MAX + 1);
1074
 
  char ievent_buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
1075
 
  struct inotify_event *ievent = ((struct inotify_event *)
1076
 
                                  ievent_buffer);
 
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;
1077
1100
 
 
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
1078
1106
  const ssize_t read_length = read(fd, ievent, ievent_size);
 
1107
#if defined(__GNUC__) and __GNUC__ >= 7
 
1108
#pragma GCC diagnostic pop
 
1109
#endif
1079
1110
  if(read_length == 0){ /* EOF */
1080
1111
    error(0, 0, "Got EOF from inotify fd for directory %s", filename);
1081
1112
    *quit_now = true;
1117
1148
             immediately */
1118
1149
          queue->next_run = 1;
1119
1150
        }
1120
 
      } else if(ievent->mask & IN_DELETE){
 
1151
      } else if(ievent->mask & (IN_MOVED_FROM | IN_DELETE)){
1121
1152
        if(not string_set_add(cancelled_filenames,
1122
1153
                              question_filename)){
1123
1154
          error(0, errno, "Could not add question %s to"
1173
1204
  bool *const password_is_read = task.password_is_read;
1174
1205
 
1175
1206
  /* We use the GLib "Key-value file parser" functions to parse the
1176
 
     question file.  See <https://www.freedesktop.org/wiki/Software
1177
 
     /systemd/PasswordAgents/> for specification of contents */
 
1207
     question file.  See <https://systemd.io/PASSWORD_AGENTS/> for
 
1208
     specification of contents */
1178
1209
  __attribute__((nonnull))
1179
1210
    void cleanup_g_key_file(GKeyFile **key_file){
1180
1211
    if(*key_file != NULL){
1457
1488
    if(send_buffer == NULL){
1458
1489
      error(0, errno, "Failed to allocate send_buffer");
1459
1490
    } 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
1460
1496
      if(mlock(send_buffer, send_buffer_length) != 0){
 
1497
#if defined(__GNUC__) and __GNUC__ >= 5
 
1498
#pragma GCC diagnostic pop
 
1499
#endif
1461
1500
        /* Warn but do not treat as fatal error */
1462
1501
        if(errno != EPERM and errno != ENOMEM){
1463
1502
          error(0, errno, "Failed to lock memory for password"
1470
1509
         not. You may but don't have to include a final NUL byte in
1471
1510
         your message.
1472
1511
 
1473
 
         — <https://www.freedesktop.org/wiki/Software/systemd/
1474
 
         PasswordAgents/> (Wed 08 Oct 2014 02:14:28 AM UTC)
 
1512
         — <https://systemd.io/PASSWORD_AGENTS/> (Tue, 15 Sep 2020
 
1513
         14:24:20 GMT)
1475
1514
      */
1476
1515
      send_buffer[0] = '+';     /* Prefix with "+" */
1477
1516
      /* Always add an extra NUL */
1482
1521
      errno = 0;
1483
1522
      ssize_t ssret = send(fd, send_buffer, send_buffer_length,
1484
1523
                           MSG_NOSIGNAL);
1485
 
      const error_t saved_errno = errno;
 
1524
      const error_t saved_errno = (ssret < 0) ? errno : 0;
1486
1525
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 25)
1487
1526
      explicit_bzero(send_buffer, send_buffer_length);
1488
1527
#else
1506
1545
          /* Retry, below */
1507
1546
          break;
1508
1547
        case EMSGSIZE:
1509
 
          error(0, 0, "Password of size %" PRIuMAX " is too big",
1510
 
                (uintmax_t)password->length);
 
1548
          error(0, saved_errno, "Password of size %" PRIuMAX
 
1549
                " is too big", (uintmax_t)password->length);
1511
1550
#if __GNUC__ < 7
1512
1551
          /* FALLTHROUGH */
1513
1552
#else
1515
1554
#endif
1516
1555
        case 0:
1517
1556
          if(ssret >= 0 and ssret < (ssize_t)send_buffer_length){
1518
 
            error(0, 0, "Password only partially sent to socket");
 
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);
1519
1560
          }
1520
1561
#if __GNUC__ < 7
1521
1562
          /* FALLTHROUGH */
1877
1918
  g_assert_true(queue->tasks[0].func == dummy_func);
1878
1919
}
1879
1920
 
 
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
 
1880
1944
static void dummy_func(__attribute__((unused))
1881
1945
                       const task_context task,
1882
1946
                       __attribute__((unused))
2153
2217
    }
2154
2218
    exit(EXIT_SUCCESS);
2155
2219
  }
 
2220
  if(pid == -1){
 
2221
    error(EXIT_FAILURE, errno, "Failed to fork()");
 
2222
  }
 
2223
 
2156
2224
  int status;
2157
2225
  waitpid(pid, &status, 0);
2158
2226
  if(WIFEXITED(status) and (WEXITSTATUS(status) == EXIT_SUCCESS)){
2220
2288
 
2221
2289
  {
2222
2290
    __attribute__((cleanup(cleanup_close)))
2223
 
      const int devnull_fd = open("/dev/null", O_WRONLY | O_CLOEXEC);
 
2291
      const int devnull_fd = open("/dev/null",
 
2292
                                  O_WRONLY | O_CLOEXEC | O_NOCTTY);
2224
2293
    g_assert_cmpint(devnull_fd, >=, 0);
2225
2294
    __attribute__((cleanup(cleanup_close)))
2226
2295
      const int real_stderr_fd = dup(STDERR_FILENO);
2250
2319
    {
2251
2320
      __attribute__((cleanup(cleanup_close)))
2252
2321
        const int devnull_fd = open("/dev/null",
2253
 
                                    O_WRONLY | O_CLOEXEC);
 
2322
                                    O_WRONLY | O_CLOEXEC | O_NOCTTY);
2254
2323
      g_assert_cmpint(devnull_fd, >=, 0);
2255
2324
      __attribute__((cleanup(cleanup_close)))
2256
2325
        const int real_stderr_fd = dup(STDERR_FILENO);
2600
2669
  bool password_is_read = false;
2601
2670
  const char helper_directory[] = "/nonexistent";
2602
2671
  const char *const argv[] = { "/bin/sh", "-c",
2603
 
    "echo -n ${MANDOSPLUGINHELPERDIR}", NULL };
 
2672
    "printf %s \"${MANDOSPLUGINHELPERDIR}\"", NULL };
2604
2673
 
2605
2674
  const bool success = start_mandos_client(queue, epoll_fd,
2606
2675
                                           &mandos_client_exited,
2901
2970
 
2902
2971
  __attribute__((cleanup(cleanup_close)))
2903
2972
    const int devnull_fd = open("/dev/null",
2904
 
                                O_WRONLY | O_CLOEXEC);
 
2973
                                O_WRONLY | O_CLOEXEC | O_NOCTTY);
2905
2974
  g_assert_cmpint(devnull_fd, >=, 0);
2906
2975
  __attribute__((cleanup(cleanup_close)))
2907
2976
    const int real_stderr_fd = dup(STDERR_FILENO);
2972
3041
 
2973
3042
  __attribute__((cleanup(cleanup_close)))
2974
3043
    const int devnull_fd = open("/dev/null",
2975
 
                                O_WRONLY | O_CLOEXEC);
 
3044
                                O_WRONLY | O_CLOEXEC, O_NOCTTY);
2976
3045
  g_assert_cmpint(devnull_fd, >=, 0);
2977
3046
  __attribute__((cleanup(cleanup_close)))
2978
3047
    const int real_stderr_fd = dup(STDERR_FILENO);
3016
3085
    buffer password = {};
3017
3086
 
3018
3087
  /* Reading /proc/self/mem from offset 0 will always give EIO */
3019
 
  const int fd = open("/proc/self/mem", O_RDONLY | O_CLOEXEC);
 
3088
  const int fd = open("/proc/self/mem",
 
3089
                      O_RDONLY | O_CLOEXEC | O_NOCTTY);
3020
3090
 
3021
3091
  bool password_is_read = false;
3022
3092
  bool quit_now = false;
3450
3520
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
3451
3521
}
3452
3522
 
 
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
 
3453
3561
static void test_add_inotify_dir_watch_EAGAIN(__attribute__((unused))
3454
3562
                                              test_fixture *fixture,
3455
3563
                                              __attribute__((unused))
3670
3778
}
3671
3779
 
3672
3780
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
3673
3853
void test_add_inotify_dir_watch_IN_DELETE(__attribute__((unused))
3674
3854
                                          test_fixture *fixture,
3675
3855
                                          __attribute__((unused))
3733
3913
  g_assert_cmpint(rmdir(tempdir), ==, 0);
3734
3914
}
3735
3915
 
 
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
 
3736
3992
static void test_read_inotify_event_readerror(__attribute__((unused))
3737
3993
                                              test_fixture *fixture,
3738
3994
                                              __attribute__((unused))
3744
4000
  const mono_microsecs current_time = 0;
3745
4001
 
3746
4002
  /* Reading /proc/self/mem from offset 0 will always result in EIO */
3747
 
  const int fd = open("/proc/self/mem", O_RDONLY | O_CLOEXEC);
 
4003
  const int fd = open("/proc/self/mem",
 
4004
                      O_RDONLY | O_CLOEXEC | O_NOCTTY);
3748
4005
 
3749
4006
  bool quit_now = false;
3750
4007
  __attribute__((cleanup(cleanup_queue)))
3929
4186
  const size_t ievent_max_size = (sizeof(struct inotify_event)
3930
4187
                                  + NAME_MAX + 1);
3931
4188
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
3932
 
  char ievent_buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
3933
 
  struct inotify_event *ievent = ((struct inotify_event *)
3934
 
                                  ievent_buffer);
 
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;
3935
4194
 
3936
4195
  const char dummy_file_name[] = "ask.dummy_file_name";
3937
4196
  ievent->mask = IN_CLOSE_WRITE;
3939
4198
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
3940
4199
  const size_t ievent_size = (sizeof(struct inotify_event)
3941
4200
                              + sizeof(dummy_file_name));
3942
 
  g_assert_cmpint(write(pipefds[1], ievent_buffer, ievent_size),
 
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),
3943
4207
                  ==, ievent_size);
 
4208
#if defined(__GNUC__) and __GNUC__ >= 11
 
4209
#pragma GCC diagnostic pop
 
4210
#endif
3944
4211
  g_assert_cmpint(close(pipefds[1]), ==, 0);
3945
4212
 
3946
4213
  bool quit_now = false;
4022
4289
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4023
4290
                                  + NAME_MAX + 1);
4024
4291
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
4025
 
  char ievent_buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
4026
 
  struct inotify_event *ievent = ((struct inotify_event *)
4027
 
                                  ievent_buffer);
 
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;
4028
4297
 
4029
4298
  const char dummy_file_name[] = "ask.dummy_file_name";
4030
4299
  ievent->mask = IN_MOVED_TO;
4032
4301
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4033
4302
  const size_t ievent_size = (sizeof(struct inotify_event)
4034
4303
                              + sizeof(dummy_file_name));
4035
 
  g_assert_cmpint(write(pipefds[1], ievent_buffer, ievent_size),
 
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),
4036
4310
                  ==, ievent_size);
 
4311
#if defined(__GNUC__) and __GNUC__ >= 11
 
4312
#pragma GCC diagnostic pop
 
4313
#endif
4037
4314
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4038
4315
 
4039
4316
  bool quit_now = false;
4098
4375
      }));
4099
4376
}
4100
4377
 
 
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
 
4101
4471
static void test_read_inotify_event_IN_DELETE(__attribute__((unused))
4102
4472
                                              test_fixture *fixture,
4103
4473
                                              __attribute__((unused))
4117
4487
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4118
4488
                                  + NAME_MAX + 1);
4119
4489
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
4120
 
  char ievent_buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
4121
 
  struct inotify_event *ievent = ((struct inotify_event *)
4122
 
                                  ievent_buffer);
 
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;
4123
4495
 
4124
4496
  const char dummy_file_name[] = "ask.dummy_file_name";
4125
4497
  ievent->mask = IN_DELETE;
4127
4499
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4128
4500
  const size_t ievent_size = (sizeof(struct inotify_event)
4129
4501
                              + sizeof(dummy_file_name));
4130
 
  g_assert_cmpint(write(pipefds[1], ievent_buffer, ievent_size),
 
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),
4131
4508
                  ==, ievent_size);
 
4509
#if defined(__GNUC__) and __GNUC__ >= 11
 
4510
#pragma GCC diagnostic pop
 
4511
#endif
4132
4512
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4133
4513
 
4134
4514
  bool quit_now = false;
4199
4579
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4200
4580
                                  + NAME_MAX + 1);
4201
4581
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
4202
 
  char ievent_buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
4203
 
  struct inotify_event *ievent = ((struct inotify_event *)
4204
 
                                  ievent_buffer);
 
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;
4205
4587
 
4206
4588
  const char dummy_file_name[] = "ignored.dummy_file_name";
4207
4589
  ievent->mask = IN_CLOSE_WRITE;
4209
4591
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4210
4592
  const size_t ievent_size = (sizeof(struct inotify_event)
4211
4593
                              + sizeof(dummy_file_name));
4212
 
  g_assert_cmpint(write(pipefds[1], ievent_buffer, ievent_size),
 
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),
4213
4600
                  ==, ievent_size);
 
4601
#if defined(__GNUC__) and __GNUC__ >= 11
 
4602
#pragma GCC diagnostic pop
 
4603
#endif
4214
4604
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4215
4605
 
4216
4606
  bool quit_now = false;
4273
4663
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4274
4664
                                  + NAME_MAX + 1);
4275
4665
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
4276
 
  char ievent_buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
4277
 
  struct inotify_event *ievent = ((struct inotify_event *)
4278
 
                                  ievent_buffer);
 
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;
4279
4671
 
4280
4672
  const char dummy_file_name[] = "ignored.dummy_file_name";
4281
4673
  ievent->mask = IN_MOVED_TO;
4283
4675
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4284
4676
  const size_t ievent_size = (sizeof(struct inotify_event)
4285
4677
                              + sizeof(dummy_file_name));
4286
 
  g_assert_cmpint(write(pipefds[1], ievent_buffer, ievent_size),
 
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),
4287
4684
                  ==, ievent_size);
 
4685
#if defined(__GNUC__) and __GNUC__ >= 11
 
4686
#pragma GCC diagnostic pop
 
4687
#endif
4288
4688
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4289
4689
 
4290
4690
  bool quit_now = false;
4330
4730
                                   EPOLLIN | EPOLLRDHUP));
4331
4731
}
4332
4732
 
 
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
 
4333
4826
static
4334
4827
void test_read_inotify_event_IN_DELETE_badname(__attribute__((unused))
4335
4828
                                               test_fixture *fixture,
4350
4843
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4351
4844
                                  + NAME_MAX + 1);
4352
4845
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
4353
 
  char ievent_buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
4354
 
  struct inotify_event *ievent = ((struct inotify_event *)
4355
 
                                  ievent_buffer);
 
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;
4356
4851
 
4357
4852
  const char dummy_file_name[] = "ignored.dummy_file_name";
4358
4853
  ievent->mask = IN_DELETE;
4360
4855
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4361
4856
  const size_t ievent_size = (sizeof(struct inotify_event)
4362
4857
                              + sizeof(dummy_file_name));
4363
 
  g_assert_cmpint(write(pipefds[1], ievent_buffer, ievent_size),
 
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),
4364
4864
                  ==, ievent_size);
 
4865
#if defined(__GNUC__) and __GNUC__ >= 11
 
4866
#pragma GCC diagnostic pop
 
4867
#endif
4365
4868
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4366
4869
 
4367
4870
  bool quit_now = false;
5253
5756
                                            __attribute__((unused))
5254
5757
                                            gconstpointer user_data){
5255
5758
  __attribute__((cleanup(cleanup_close)))
5256
 
    const int epoll_fd = open("/dev/null", O_WRONLY | O_CLOEXEC);
 
5759
    const int epoll_fd = open("/dev/null",
 
5760
                              O_WRONLY | O_CLOEXEC | O_NOCTTY);
5257
5761
  __attribute__((cleanup(cleanup_string)))
5258
5762
    char *const question_filename = strdup("/nonexistent/question");
5259
5763
  g_assert_nonnull(question_filename);
5388
5892
  char write_data[PIPE_BUF];
5389
5893
  {
5390
5894
    /* Construct test password buffer */
5391
 
    /* Start with + since that is what the real procotol uses */
 
5895
    /* Start with + since that is what the real protocol uses */
5392
5896
    write_data[0] = '+';
5393
5897
    /* Set a special character at string end just to mark the end */
5394
5898
    write_data[sizeof(write_data)-2] = 'y';
5546
6050
  char *const filename = strdup("/nonexistent/socket");
5547
6051
  __attribute__((cleanup(string_set_clear)))
5548
6052
    string_set cancelled_filenames = {};
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,
 
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,
5555
6109
  };
5556
 
  g_assert_nonnull(password.data);
5557
6110
  if(mlock(password.data, password.allocated) != 0){
5558
6111
    g_assert_true(errno == EPERM or errno == ENOMEM);
5559
6112
  }
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);
5569
6113
 
5570
6114
  __attribute__((cleanup(cleanup_queue)))
5571
6115
    task_queue *queue = create_queue();
5572
6116
  g_assert_nonnull(queue);
5573
 
  int socketfds[2];
5574
6117
  g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM
5575
6118
                             | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
5576
6119
                             socketfds), ==, 0);
5663
6206
                                            __attribute__((unused))
5664
6207
                                            gconstpointer user_data){
5665
6208
  __attribute__((cleanup(cleanup_close)))
5666
 
    const int epoll_fd = open("/dev/null", O_WRONLY | O_CLOEXEC);
 
6209
    const int epoll_fd = open("/dev/null",
 
6210
                              O_WRONLY | O_CLOEXEC | O_NOCTTY);
5667
6211
  __attribute__((cleanup(cleanup_string)))
5668
6212
    char *const question_filename = strdup("/nonexistent/question");
5669
6213
  g_assert_nonnull(question_filename);
5932
6476
                                              const char *const
5933
6477
                                              dirname){
5934
6478
  __attribute__((cleanup(cleanup_close)))
5935
 
    const int devnull_fd = open("/dev/null", O_WRONLY | O_CLOEXEC);
 
6479
    const int devnull_fd = open("/dev/null",
 
6480
                                O_WRONLY | O_CLOEXEC | O_NOCTTY);
5936
6481
  g_assert_cmpint(devnull_fd, >=, 0);
5937
6482
  __attribute__((cleanup(cleanup_close)))
5938
6483
    const int real_stderr_fd = dup(STDERR_FILENO);
7481
8026
  test_add("/parse_arguments/mixed", test_parse_arguments_mixed);
7482
8027
  test_add("/queue/create", test_create_queue);
7483
8028
  test_add("/queue/add", test_add_to_queue);
 
8029
  test_add("/queue/add/overflow", test_add_to_queue_overflow);
7484
8030
  test_add("/queue/has_question/empty",
7485
8031
           test_queue_has_question_empty);
7486
8032
  test_add("/queue/has_question/false",
7573
8119
              test_add_inotify_dir_watch);
7574
8120
  test_add_st("/task-creators/add_inotify_dir_watch/fail",
7575
8121
              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);
7576
8124
  test_add_st("/task-creators/add_inotify_dir_watch/EAGAIN",
7577
8125
              test_add_inotify_dir_watch_EAGAIN);
7578
8126
  test_add_st("/task-creators/add_inotify_dir_watch/IN_CLOSE_WRITE",
7579
8127
              test_add_inotify_dir_watch_IN_CLOSE_WRITE);
7580
8128
  test_add_st("/task-creators/add_inotify_dir_watch/IN_MOVED_TO",
7581
8129
              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);
7582
8134
  test_add_st("/task-creators/add_inotify_dir_watch/IN_DELETE",
7583
8135
              test_add_inotify_dir_watch_IN_DELETE);
7584
8136
  test_add_st("/task/read_inotify_event/readerror",
7593
8145
              test_read_inotify_event_IN_CLOSE_WRITE);
7594
8146
  test_add_st("/task/read_inotify_event/IN_MOVED_TO",
7595
8147
              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);
7596
8150
  test_add_st("/task/read_inotify_event/IN_DELETE",
7597
8151
              test_read_inotify_event_IN_DELETE);
7598
8152
  test_add_st("/task/read_inotify_event/IN_CLOSE_WRITE/badname",
7599
8153
              test_read_inotify_event_IN_CLOSE_WRITE_badname);
7600
8154
  test_add_st("/task/read_inotify_event/IN_MOVED_TO/badname",
7601
8155
              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);
7602
8158
  test_add_st("/task/read_inotify_event/IN_DELETE/badname",
7603
8159
              test_read_inotify_event_IN_DELETE_badname);
7604
8160
  test_add_st("/task/open_and_parse_question/ENOENT",
7701
8257
  g_option_context_set_help_enabled(context, FALSE);
7702
8258
  g_option_context_set_ignore_unknown_options(context, TRUE);
7703
8259
 
7704
 
  gboolean run_tests = FALSE;
 
8260
  gboolean should_run_tests = FALSE;
7705
8261
  GOptionEntry entries[] = {
7706
8262
    { "test", 0, 0, G_OPTION_ARG_NONE,
7707
 
      &run_tests, "Run tests", NULL },
 
8263
      &should_run_tests, "Run tests", NULL },
7708
8264
    { NULL }
7709
8265
  };
7710
8266
  g_option_context_add_main_entries(context, entries, NULL);
7717
8273
  }
7718
8274
 
7719
8275
  g_option_context_free(context);
7720
 
  return run_tests != FALSE;
 
8276
  return should_run_tests != FALSE;
7721
8277
}
 
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
*/