/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: 2024-11-17 18:43:11 UTC
  • mto: This revision was merged to the branch mainline in revision 412.
  • Revision ID: teddy@recompile.se-20241117184311-ox25kvngy62h209g
Debian package: Avoid suggesting a C compiler unnecessarily

The list of suggested packages, meant to enable the "mandos" program
to find the correct value of SO_BINDTODEVICE by using a C compiler,
are not necessary when Python 3.3 or later is used, since it has the
SO_BINDTODEVICE constant defined in the "socket" module.  Also, Python
2.6 or older has the same constant in the old "IN" module.  Therefore,
we should suggest these Python versions as alternatives to a C
compiler, so that a C compiler is not installed unnecessarily.

debian/control (Package: mandos/Suggests): Add "python3 (>= 3.3)" and
"python (<= 2.6)" as alternatives to "libc6-dev | libc-dev" and
"c-compiler".

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;
1077
1098
  } ievent_buffer;
1078
1099
  struct inotify_event *const ievent = &ievent_buffer.event;
1079
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
1080
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
1081
1110
  if(read_length == 0){ /* EOF */
1082
1111
    error(0, 0, "Got EOF from inotify fd for directory %s", filename);
1083
1112
    *quit_now = true;
1119
1148
             immediately */
1120
1149
          queue->next_run = 1;
1121
1150
        }
1122
 
      } else if(ievent->mask & IN_DELETE){
 
1151
      } else if(ievent->mask & (IN_MOVED_FROM | IN_DELETE)){
1123
1152
        if(not string_set_add(cancelled_filenames,
1124
1153
                              question_filename)){
1125
1154
          error(0, errno, "Could not add question %s to"
1175
1204
  bool *const password_is_read = task.password_is_read;
1176
1205
 
1177
1206
  /* We use the GLib "Key-value file parser" functions to parse the
1178
 
     question file.  See <https://www.freedesktop.org/wiki/Software
1179
 
     /systemd/PasswordAgents/> for specification of contents */
 
1207
     question file.  See <https://systemd.io/PASSWORD_AGENTS/> for
 
1208
     specification of contents */
1180
1209
  __attribute__((nonnull))
1181
1210
    void cleanup_g_key_file(GKeyFile **key_file){
1182
1211
    if(*key_file != NULL){
1459
1488
    if(send_buffer == NULL){
1460
1489
      error(0, errno, "Failed to allocate send_buffer");
1461
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
1462
1496
      if(mlock(send_buffer, send_buffer_length) != 0){
 
1497
#if defined(__GNUC__) and __GNUC__ >= 5
 
1498
#pragma GCC diagnostic pop
 
1499
#endif
1463
1500
        /* Warn but do not treat as fatal error */
1464
1501
        if(errno != EPERM and errno != ENOMEM){
1465
1502
          error(0, errno, "Failed to lock memory for password"
1472
1509
         not. You may but don't have to include a final NUL byte in
1473
1510
         your message.
1474
1511
 
1475
 
         — <https://www.freedesktop.org/wiki/Software/systemd/
1476
 
         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)
1477
1514
      */
1478
1515
      send_buffer[0] = '+';     /* Prefix with "+" */
1479
1516
      /* Always add an extra NUL */
1484
1521
      errno = 0;
1485
1522
      ssize_t ssret = send(fd, send_buffer, send_buffer_length,
1486
1523
                           MSG_NOSIGNAL);
1487
 
      const error_t saved_errno = errno;
 
1524
      const error_t saved_errno = (ssret < 0) ? errno : 0;
1488
1525
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 25)
1489
1526
      explicit_bzero(send_buffer, send_buffer_length);
1490
1527
#else
1508
1545
          /* Retry, below */
1509
1546
          break;
1510
1547
        case EMSGSIZE:
1511
 
          error(0, 0, "Password of size %" PRIuMAX " is too big",
1512
 
                (uintmax_t)password->length);
 
1548
          error(0, saved_errno, "Password of size %" PRIuMAX
 
1549
                " is too big", (uintmax_t)password->length);
1513
1550
#if __GNUC__ < 7
1514
1551
          /* FALLTHROUGH */
1515
1552
#else
1517
1554
#endif
1518
1555
        case 0:
1519
1556
          if(ssret >= 0 and ssret < (ssize_t)send_buffer_length){
1520
 
            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);
1521
1560
          }
1522
1561
#if __GNUC__ < 7
1523
1562
          /* FALLTHROUGH */
1879
1918
  g_assert_true(queue->tasks[0].func == dummy_func);
1880
1919
}
1881
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
 
1882
1944
static void dummy_func(__attribute__((unused))
1883
1945
                       const task_context task,
1884
1946
                       __attribute__((unused))
2155
2217
    }
2156
2218
    exit(EXIT_SUCCESS);
2157
2219
  }
 
2220
  if(pid == -1){
 
2221
    error(EXIT_FAILURE, errno, "Failed to fork()");
 
2222
  }
 
2223
 
2158
2224
  int status;
2159
2225
  waitpid(pid, &status, 0);
2160
2226
  if(WIFEXITED(status) and (WEXITSTATUS(status) == EXIT_SUCCESS)){
2222
2288
 
2223
2289
  {
2224
2290
    __attribute__((cleanup(cleanup_close)))
2225
 
      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);
2226
2293
    g_assert_cmpint(devnull_fd, >=, 0);
2227
2294
    __attribute__((cleanup(cleanup_close)))
2228
2295
      const int real_stderr_fd = dup(STDERR_FILENO);
2252
2319
    {
2253
2320
      __attribute__((cleanup(cleanup_close)))
2254
2321
        const int devnull_fd = open("/dev/null",
2255
 
                                    O_WRONLY | O_CLOEXEC);
 
2322
                                    O_WRONLY | O_CLOEXEC | O_NOCTTY);
2256
2323
      g_assert_cmpint(devnull_fd, >=, 0);
2257
2324
      __attribute__((cleanup(cleanup_close)))
2258
2325
        const int real_stderr_fd = dup(STDERR_FILENO);
2602
2669
  bool password_is_read = false;
2603
2670
  const char helper_directory[] = "/nonexistent";
2604
2671
  const char *const argv[] = { "/bin/sh", "-c",
2605
 
    "echo -n ${MANDOSPLUGINHELPERDIR}", NULL };
 
2672
    "printf %s \"${MANDOSPLUGINHELPERDIR}\"", NULL };
2606
2673
 
2607
2674
  const bool success = start_mandos_client(queue, epoll_fd,
2608
2675
                                           &mandos_client_exited,
2903
2970
 
2904
2971
  __attribute__((cleanup(cleanup_close)))
2905
2972
    const int devnull_fd = open("/dev/null",
2906
 
                                O_WRONLY | O_CLOEXEC);
 
2973
                                O_WRONLY | O_CLOEXEC | O_NOCTTY);
2907
2974
  g_assert_cmpint(devnull_fd, >=, 0);
2908
2975
  __attribute__((cleanup(cleanup_close)))
2909
2976
    const int real_stderr_fd = dup(STDERR_FILENO);
2974
3041
 
2975
3042
  __attribute__((cleanup(cleanup_close)))
2976
3043
    const int devnull_fd = open("/dev/null",
2977
 
                                O_WRONLY | O_CLOEXEC);
 
3044
                                O_WRONLY | O_CLOEXEC, O_NOCTTY);
2978
3045
  g_assert_cmpint(devnull_fd, >=, 0);
2979
3046
  __attribute__((cleanup(cleanup_close)))
2980
3047
    const int real_stderr_fd = dup(STDERR_FILENO);
3018
3085
    buffer password = {};
3019
3086
 
3020
3087
  /* Reading /proc/self/mem from offset 0 will always give EIO */
3021
 
  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);
3022
3090
 
3023
3091
  bool password_is_read = false;
3024
3092
  bool quit_now = false;
3452
3520
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
3453
3521
}
3454
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
 
3455
3561
static void test_add_inotify_dir_watch_EAGAIN(__attribute__((unused))
3456
3562
                                              test_fixture *fixture,
3457
3563
                                              __attribute__((unused))
3672
3778
}
3673
3779
 
3674
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
3675
3853
void test_add_inotify_dir_watch_IN_DELETE(__attribute__((unused))
3676
3854
                                          test_fixture *fixture,
3677
3855
                                          __attribute__((unused))
3735
3913
  g_assert_cmpint(rmdir(tempdir), ==, 0);
3736
3914
}
3737
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
 
3738
3992
static void test_read_inotify_event_readerror(__attribute__((unused))
3739
3993
                                              test_fixture *fixture,
3740
3994
                                              __attribute__((unused))
3746
4000
  const mono_microsecs current_time = 0;
3747
4001
 
3748
4002
  /* Reading /proc/self/mem from offset 0 will always result in EIO */
3749
 
  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);
3750
4005
 
3751
4006
  bool quit_now = false;
3752
4007
  __attribute__((cleanup(cleanup_queue)))
3943
4198
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
3944
4199
  const size_t ievent_size = (sizeof(struct inotify_event)
3945
4200
                              + 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
3946
4206
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
3947
4207
                  ==, ievent_size);
 
4208
#if defined(__GNUC__) and __GNUC__ >= 11
 
4209
#pragma GCC diagnostic pop
 
4210
#endif
3948
4211
  g_assert_cmpint(close(pipefds[1]), ==, 0);
3949
4212
 
3950
4213
  bool quit_now = false;
4038
4301
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4039
4302
  const size_t ievent_size = (sizeof(struct inotify_event)
4040
4303
                              + 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
4041
4309
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
4042
4310
                  ==, ievent_size);
 
4311
#if defined(__GNUC__) and __GNUC__ >= 11
 
4312
#pragma GCC diagnostic pop
 
4313
#endif
4043
4314
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4044
4315
 
4045
4316
  bool quit_now = false;
4104
4375
      }));
4105
4376
}
4106
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
 
4107
4471
static void test_read_inotify_event_IN_DELETE(__attribute__((unused))
4108
4472
                                              test_fixture *fixture,
4109
4473
                                              __attribute__((unused))
4135
4499
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4136
4500
  const size_t ievent_size = (sizeof(struct inotify_event)
4137
4501
                              + 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
4138
4507
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
4139
4508
                  ==, ievent_size);
 
4509
#if defined(__GNUC__) and __GNUC__ >= 11
 
4510
#pragma GCC diagnostic pop
 
4511
#endif
4140
4512
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4141
4513
 
4142
4514
  bool quit_now = false;
4219
4591
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4220
4592
  const size_t ievent_size = (sizeof(struct inotify_event)
4221
4593
                              + 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
4222
4599
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
4223
4600
                  ==, ievent_size);
 
4601
#if defined(__GNUC__) and __GNUC__ >= 11
 
4602
#pragma GCC diagnostic pop
 
4603
#endif
4224
4604
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4225
4605
 
4226
4606
  bool quit_now = false;
4295
4675
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4296
4676
  const size_t ievent_size = (sizeof(struct inotify_event)
4297
4677
                              + 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
4298
4683
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
4299
4684
                  ==, ievent_size);
 
4685
#if defined(__GNUC__) and __GNUC__ >= 11
 
4686
#pragma GCC diagnostic pop
 
4687
#endif
4300
4688
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4301
4689
 
4302
4690
  bool quit_now = false;
4342
4730
                                   EPOLLIN | EPOLLRDHUP));
4343
4731
}
4344
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
 
4345
4826
static
4346
4827
void test_read_inotify_event_IN_DELETE_badname(__attribute__((unused))
4347
4828
                                               test_fixture *fixture,
4374
4855
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4375
4856
  const size_t ievent_size = (sizeof(struct inotify_event)
4376
4857
                              + 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
4377
4863
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
4378
4864
                  ==, ievent_size);
 
4865
#if defined(__GNUC__) and __GNUC__ >= 11
 
4866
#pragma GCC diagnostic pop
 
4867
#endif
4379
4868
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4380
4869
 
4381
4870
  bool quit_now = false;
5267
5756
                                            __attribute__((unused))
5268
5757
                                            gconstpointer user_data){
5269
5758
  __attribute__((cleanup(cleanup_close)))
5270
 
    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);
5271
5761
  __attribute__((cleanup(cleanup_string)))
5272
5762
    char *const question_filename = strdup("/nonexistent/question");
5273
5763
  g_assert_nonnull(question_filename);
5402
5892
  char write_data[PIPE_BUF];
5403
5893
  {
5404
5894
    /* Construct test password buffer */
5405
 
    /* Start with + since that is what the real procotol uses */
 
5895
    /* Start with + since that is what the real protocol uses */
5406
5896
    write_data[0] = '+';
5407
5897
    /* Set a special character at string end just to mark the end */
5408
5898
    write_data[sizeof(write_data)-2] = 'y';
5560
6050
  char *const filename = strdup("/nonexistent/socket");
5561
6051
  __attribute__((cleanup(string_set_clear)))
5562
6052
    string_set cancelled_filenames = {};
5563
 
  const size_t oversized = 1024*1024; /* Limit seems to be 212960 */
5564
 
  __attribute__((cleanup(cleanup_buffer)))
5565
 
    buffer password = {
5566
 
    .data=malloc(oversized),
5567
 
    .length=oversized,
5568
 
    .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,
5569
6109
  };
5570
 
  g_assert_nonnull(password.data);
5571
6110
  if(mlock(password.data, password.allocated) != 0){
5572
6111
    g_assert_true(errno == EPERM or errno == ENOMEM);
5573
6112
  }
5574
 
  /* Construct test password buffer */
5575
 
  /* Start with + since that is what the real procotol uses */
5576
 
  password.data[0] = '+';
5577
 
  /* Set a special character at string end just to mark the end */
5578
 
  password.data[oversized-3] = 'y';
5579
 
  /* Set NUL at buffer end, as suggested by the protocol */
5580
 
  password.data[oversized-2] = '\0';
5581
 
  /* Fill rest of password with 'x' */
5582
 
  memset(password.data+1, 'x', oversized-3);
5583
6113
 
5584
6114
  __attribute__((cleanup(cleanup_queue)))
5585
6115
    task_queue *queue = create_queue();
5586
6116
  g_assert_nonnull(queue);
5587
 
  int socketfds[2];
5588
6117
  g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM
5589
6118
                             | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
5590
6119
                             socketfds), ==, 0);
5677
6206
                                            __attribute__((unused))
5678
6207
                                            gconstpointer user_data){
5679
6208
  __attribute__((cleanup(cleanup_close)))
5680
 
    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);
5681
6211
  __attribute__((cleanup(cleanup_string)))
5682
6212
    char *const question_filename = strdup("/nonexistent/question");
5683
6213
  g_assert_nonnull(question_filename);
5946
6476
                                              const char *const
5947
6477
                                              dirname){
5948
6478
  __attribute__((cleanup(cleanup_close)))
5949
 
    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);
5950
6481
  g_assert_cmpint(devnull_fd, >=, 0);
5951
6482
  __attribute__((cleanup(cleanup_close)))
5952
6483
    const int real_stderr_fd = dup(STDERR_FILENO);
7495
8026
  test_add("/parse_arguments/mixed", test_parse_arguments_mixed);
7496
8027
  test_add("/queue/create", test_create_queue);
7497
8028
  test_add("/queue/add", test_add_to_queue);
 
8029
  test_add("/queue/add/overflow", test_add_to_queue_overflow);
7498
8030
  test_add("/queue/has_question/empty",
7499
8031
           test_queue_has_question_empty);
7500
8032
  test_add("/queue/has_question/false",
7587
8119
              test_add_inotify_dir_watch);
7588
8120
  test_add_st("/task-creators/add_inotify_dir_watch/fail",
7589
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);
7590
8124
  test_add_st("/task-creators/add_inotify_dir_watch/EAGAIN",
7591
8125
              test_add_inotify_dir_watch_EAGAIN);
7592
8126
  test_add_st("/task-creators/add_inotify_dir_watch/IN_CLOSE_WRITE",
7593
8127
              test_add_inotify_dir_watch_IN_CLOSE_WRITE);
7594
8128
  test_add_st("/task-creators/add_inotify_dir_watch/IN_MOVED_TO",
7595
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);
7596
8134
  test_add_st("/task-creators/add_inotify_dir_watch/IN_DELETE",
7597
8135
              test_add_inotify_dir_watch_IN_DELETE);
7598
8136
  test_add_st("/task/read_inotify_event/readerror",
7607
8145
              test_read_inotify_event_IN_CLOSE_WRITE);
7608
8146
  test_add_st("/task/read_inotify_event/IN_MOVED_TO",
7609
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);
7610
8150
  test_add_st("/task/read_inotify_event/IN_DELETE",
7611
8151
              test_read_inotify_event_IN_DELETE);
7612
8152
  test_add_st("/task/read_inotify_event/IN_CLOSE_WRITE/badname",
7613
8153
              test_read_inotify_event_IN_CLOSE_WRITE_badname);
7614
8154
  test_add_st("/task/read_inotify_event/IN_MOVED_TO/badname",
7615
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);
7616
8158
  test_add_st("/task/read_inotify_event/IN_DELETE/badname",
7617
8159
              test_read_inotify_event_IN_DELETE_badname);
7618
8160
  test_add_st("/task/open_and_parse_question/ENOENT",
7715
8257
  g_option_context_set_help_enabled(context, FALSE);
7716
8258
  g_option_context_set_ignore_unknown_options(context, TRUE);
7717
8259
 
7718
 
  gboolean run_tests = FALSE;
 
8260
  gboolean should_run_tests = FALSE;
7719
8261
  GOptionEntry entries[] = {
7720
8262
    { "test", 0, 0, G_OPTION_ARG_NONE,
7721
 
      &run_tests, "Run tests", NULL },
 
8263
      &should_run_tests, "Run tests", NULL },
7722
8264
    { NULL }
7723
8265
  };
7724
8266
  g_option_context_add_main_entries(context, entries, NULL);
7731
8273
  }
7732
8274
 
7733
8275
  g_option_context_free(context);
7734
 
  return run_tests != FALSE;
 
8276
  return should_run_tests != FALSE;
7735
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
*/