libfuse
test_signals.c
1/*
2 * FUSE: Filesystem in Userspace
3 * Copyright (C) 2025 Bernd Schubert <bernd@bsbernd.com>
4 *
5 * Test for signal handling in libfuse.
6 *
7 * This program can be distributed under the terms of the GNU LGPLv2.
8 * See the file GPL2.txt
9 */
10
11#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17)
12
13#include "fuse_config.h"
14#include "fuse_lowlevel.h"
15#include "fuse_i.h"
16
17#include <pthread.h>
18#include <stdatomic.h>
19#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
22#include <unistd.h>
23#include <signal.h>
24#include <errno.h>
25#include <sys/types.h>
26#include <sys/wait.h>
27
28static void test_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
29{
30 (void)parent;
31 (void)name;
32 /* Simulate slow lookup to test signal interruption */
33 sleep(2);
34 fuse_reply_err(req, ENOENT);
35}
36
37static void test_ll_getattr(fuse_req_t req, fuse_ino_t ino,
38 struct fuse_file_info *fi)
39{
40 (void)ino;
41 (void)fi;
42 /* Simulate slow getattr to test signal interruption */
43 sleep(2);
44 fuse_reply_err(req, ENOENT);
45}
46
47static const struct fuse_lowlevel_ops test_ll_ops = {
48 .lookup = test_ll_lookup,
49 .getattr = test_ll_getattr,
50};
51
52static void *signal_sender_thread(void *arg)
53{
54 (void)arg;
55
56 usleep(2 * 1000 * 1000);
57
58 /* Send SIGTERM to the process */
59 kill(getpid(), SIGTERM);
60 return NULL;
61}
62
63static void fork_child(void)
64{
65 struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
66 struct fuse_session *se;
67 struct fuse_loop_config *loop_config;
68 pthread_t sig_thread;
69 char *mountpoint = NULL;
70 int ret = -1;
71
72 /* Add the program name to arg[0] */
73 if (fuse_opt_add_arg(&args, "test_signals")) {
74 fprintf(stderr, "Failed to add argument\n");
75 goto out_free_mountpoint;
76 }
77
78 /* Add debug flag to see more output */
79 fuse_opt_add_arg(&args, "-d");
80
81 /* Create temporary mount point */
82 mountpoint = strdup("/tmp/fuse_test_XXXXXX");
83 if (!mountpoint || !mkdtemp(mountpoint)) {
84 fprintf(stderr, "Failed to create temp dir\n");
85 goto out_free_args;
86 }
87
88 /* Create session */
89 se = fuse_session_new(&args, &test_ll_ops, sizeof(test_ll_ops), NULL);
90 if (!se) {
91 fprintf(stderr, "Failed to create FUSE session\n");
92 goto out_free_mountpoint;
93 }
94
95 /* Mount filesystem */
96 if (fuse_session_mount(se, mountpoint)) {
97 fprintf(stderr, "Failed to mount FUSE filesystem\n");
98 goto out_destroy_session;
99 }
100
101 /* Create loop config */
102 loop_config = fuse_loop_cfg_create();
103 if (!loop_config) {
104 fprintf(stderr, "Failed to create loop config\n");
105 goto out_unmount;
106 }
107 fuse_loop_cfg_set_clone_fd(loop_config, 0);
108 fuse_loop_cfg_set_max_threads(loop_config, 2);
109
110 /* Set up signal handlers */
111 if (fuse_set_signal_handlers(se)) {
112 fprintf(stderr, "Failed to set up signal handlers\n");
113 goto out_destroy_config;
114 }
115
116 /* Create thread that will send signals */
117 if (pthread_create(&sig_thread, NULL, signal_sender_thread, NULL)) {
118 fprintf(stderr, "Failed to create signal sender thread\n");
119 goto out_remove_handlers;
120 }
121
122 /* Enter FUSE loop */
123 ret = fuse_session_loop_mt_312(se, loop_config);
124
125 printf("Debug: fuse_session_loop_mt_312 returned %d\n", ret);
126 printf("Debug: session exited state: %d\n", fuse_session_exited(se));
127 printf("Debug: session status: %d\n", se->error);
128
129 /* Check exit status before cleanup */
130 int clean_exit = (fuse_session_exited(se) && se->error == SIGTERM);
131
132 /* Clean up */
133 pthread_join(sig_thread, NULL);
137 fuse_loop_cfg_destroy(loop_config);
138 rmdir(mountpoint);
139 free(mountpoint);
140 fuse_opt_free_args(&args);
141
142 /* Use saved exit status */
143 if (clean_exit) {
144 printf("Debug: Clean shutdown via SIGTERM\n");
145 exit(0);
146 }
147 printf("Debug: Exiting with status %d\n", ret != 0);
148 exit(ret != 0);
149
150out_remove_handlers:
152out_destroy_config:
153 fuse_loop_cfg_destroy(loop_config);
154out_unmount:
156out_destroy_session:
158out_free_mountpoint:
159 rmdir(mountpoint);
160 free(mountpoint);
161out_free_args:
162 fuse_opt_free_args(&args);
163 exit(1);
164}
165
166static void run_test_in_child(void)
167{
168 pid_t child;
169 int status;
170
171 child = fork();
172 if (child == -1) {
173 perror("fork");
174 exit(1);
175 }
176
177 if (child == 0)
178 fork_child();
179
180 /* In parent process */
181 if (waitpid(child, &status, 0) == -1) {
182 perror("waitpid");
183 exit(1);
184 }
185
186 /* Check if child exited due to SIGTERM - this is expected */
187 if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) {
188 printf("Child process terminated by SIGTERM as expected\n");
189 exit(0);
190 }
191
192 /* For any other type of exit, maintain existing behavior */
193 exit(WIFEXITED(status) ? WEXITSTATUS(status) : 1);
194}
195
196int main(void)
197{
198 printf("Testing SIGTERM handling in libfuse\n");
199 run_test_in_child();
200 printf("SIGTERM handling test passed\n");
201 return 0;
202}
int fuse_set_signal_handlers(struct fuse_session *se)
void fuse_remove_signal_handlers(struct fuse_session *se)
void fuse_session_destroy(struct fuse_session *se)
int fuse_reply_err(fuse_req_t req, int err)
struct fuse_req * fuse_req_t
int fuse_session_exited(struct fuse_session *se)
void fuse_session_unmount(struct fuse_session *se)
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
uint64_t fuse_ino_t
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)