]> mj.ucw.cz Git - libucw.git/blob - ucw/daemon.c
Opt: Fixed compilation warnings in tests.
[libucw.git] / ucw / daemon.c
1 /*
2  *      UCW Library -- Daemonization
3  *
4  *      (c) 2012--2014 Martin Mares <mj@ucw.cz>
5  *      (c) 2014 Pavel Charvat <pchar@ucw.cz>
6  *
7  *      This software may be freely distributed and used according to the terms
8  *      of the GNU Lesser General Public License.
9  */
10
11 #include <ucw/lib.h>
12 #include <ucw/daemon.h>
13 #include <ucw/strtonum.h>
14
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <fcntl.h>
18 #include <unistd.h>
19 #include <pwd.h>
20 #include <grp.h>
21 #include <errno.h>
22 #include <string.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <sys/file.h>
26
27 void
28 daemon_resolve_ugid(struct daemon_params *dp)
29 {
30   // Resolve user name
31   const char *u = dp->run_as_user;
32   struct passwd *pw = NULL;
33   if (u)
34     {
35       if (u[0] == '#')
36         {
37           uint id;
38           const char *err = str_to_uint(&id, u, NULL, 10 | STN_WHOLE);
39           if (err)
40             die("Cannot parse user `%s': %s", u, err);
41           dp->run_as_uid = id;
42           dp->want_setuid = 1;
43         }
44       else
45         {
46           pw = getpwnam(u);
47           if (!pw)
48             die("No such user `%s'", u);
49           dp->run_as_uid = pw->pw_uid;
50           dp->want_setuid = 1;
51         }
52     }
53
54   // Resolve group name
55   const char *g = dp->run_as_group;
56   struct group *gr;
57   if (g)
58     {
59       if (g[0] == '#')
60         {
61           uint id;
62           const char *err = str_to_uint(&id, g, NULL, 10 | STN_WHOLE);
63           if (err)
64             die("Cannot parse group `%s': %s", g, err);
65           dp->run_as_gid = id;
66           dp->want_setgid = 1;
67         }
68       else
69         {
70           gr = getgrnam(g);
71           if (!gr)
72             die("No such group `%s'", g);
73           dp->run_as_gid = gr->gr_gid;
74           dp->want_setgid = 1;
75         }
76     }
77   else if (pw)
78     {
79       dp->run_as_gid = pw->pw_gid;
80       dp->want_setgid = 2;
81     }
82 }
83
84 void
85 daemon_switch_ugid(struct daemon_params *dp)
86 {
87   if (dp->want_setgid && setresgid(dp->run_as_gid, dp->run_as_gid, dp->run_as_gid) < 0)
88     die("Cannot set GID to %d: %m", (int) dp->run_as_gid);
89   if (dp->want_setgid > 1 && initgroups(dp->run_as_user, dp->run_as_gid) < 0)
90     die("Cannot initialize groups: %m");
91   if (dp->want_setuid && setresuid(dp->run_as_uid, dp->run_as_uid, dp->run_as_uid) < 0)
92     die("Cannot set UID to %d: %m", (int) dp->run_as_uid);
93 }
94
95 void
96 daemon_init(struct daemon_params *dp)
97 {
98   daemon_resolve_ugid(dp);
99
100   if (dp->flags & DAEMON_FLAG_SIMULATE)
101     return;
102
103   if (dp->pid_file)
104     {
105       // Check that PID file path is absolute
106       if (!(dp->flags & DAEMON_FLAG_PRESERVE_CWD) && dp->pid_file[0] != '/')
107         die("Path to PID file `%s' must be absolute", dp->pid_file);
108
109       // Open PID file
110       dp->pid_fd = open(dp->pid_file, O_RDWR | O_CREAT, 0666);
111       if (dp->pid_fd < 0)
112         die("Cannot open `%s': %m", dp->pid_file);
113       int fl = fcntl(dp->pid_fd, F_GETFD);
114       if (fl < 0 || fcntl(dp->pid_fd, F_SETFD, fl | FD_CLOEXEC))
115         die("Cannot set FD_CLOEXEC: %m");
116
117       // Try to lock it with an exclusive lock
118       if (flock(dp->pid_fd, LOCK_EX | LOCK_NB) < 0)
119         {
120           if (errno == EINTR || errno == EWOULDBLOCK)
121             die("Daemon is already running (`%s' locked)", dp->pid_file);
122           else
123             die("Cannot lock `%s': %m", dp->pid_file);
124         }
125
126       // Make a note that the daemon is starting
127       if (ftruncate(dp->pid_fd, 0) < 0 ||
128           write(dp->pid_fd, "(starting)\n", 11) != 11)
129         die("Error writing `%s': %m", dp->pid_file);
130     }
131 }
132
133 void
134 daemon_run(struct daemon_params *dp, void (*body)(struct daemon_params *dp))
135 {
136   if (dp->flags & DAEMON_FLAG_SIMULATE)
137     {
138       body(dp);
139       return;
140     }
141
142   // Switch GID and UID
143   daemon_switch_ugid(dp);
144
145   // Create a new session and close stdio
146   setsid();
147   close(0);
148   if (open("/dev/null", O_RDWR, 0) < 0 ||
149       dup2(0, 1) < 0)
150     die("Cannot redirect stdio to `/dev/null': %m");
151
152   // Set umask to a reasonable value
153   umask(022);
154
155   // Do not hold the current working directory
156   if (!(dp->flags & DAEMON_FLAG_PRESERVE_CWD))
157     {
158       if (chdir("/") < 0)
159         die("Cannot chdir to root: %m");
160     }
161
162   // Create pipe to synchronize child process with master and avoid possible
163   // collision between writing of PID and daemon_exit()
164   int pipe_fd[2];
165   if (dp->pid_file && pipe(pipe_fd) < 0)
166     die("Cannot create pipe: %m");
167
168   // Fork
169   pid_t pid = fork();
170   if (pid < 0)
171     die("Cannot fork: %m");
172   if (!pid)
173     {
174       // Wait for master process to finish writing of PID
175       if (dp->pid_file)
176         {
177           byte pipe_buf[1];
178           close(pipe_fd[1]);
179           if (read(pipe_fd[0], pipe_buf, 1) < 0)
180             die("Cannot read pipe: %m");
181           close(pipe_fd[0]);
182         }
183
184       // We still keep the PID file open and thus locked
185       body(dp);
186       exit(0);
187     }
188
189   // Write PID
190   if (dp->pid_file)
191     {
192       char buf[32];
193       int c = snprintf(buf, sizeof(buf), "%d\n", (int) pid);
194       ASSERT(c <= (int) sizeof(buf));
195       if (lseek(dp->pid_fd, 0, SEEK_SET) < 0 ||
196           write(dp->pid_fd, buf, c) != c ||
197           ftruncate(dp->pid_fd, c) ||
198           close(dp->pid_fd) < 0)
199         die("Cannot write PID to `%s': %m", dp->pid_file);
200       close(pipe_fd[0]);
201       close(pipe_fd[1]);
202     }
203 }
204
205 void
206 daemon_exit(struct daemon_params *dp)
207 {
208   if (dp->flags & DAEMON_FLAG_SIMULATE)
209     return;
210
211   if (dp->pid_file)
212     {
213       if (ftruncate(dp->pid_fd, 0))
214         die("Error truncating `%s': %m", dp->pid_file);
215       close(dp->pid_fd);
216     }
217 }
218
219 #ifdef TEST
220
221 #include <signal.h>
222
223 static volatile sig_atomic_t terminate;
224
225 static void term_handler(int sig UNUSED)
226 {
227   msg(L_INFO | L_SIGHANDLER, "SIGTERM received, terminating in a while");
228   terminate = 1;
229 }
230
231 static void hup_handler(int sig UNUSED)
232 {
233   msg(L_INFO | L_SIGHANDLER, "SIGHUP received");
234 }
235
236 static void body(struct daemon_params *dp)
237 {
238   log_fork();
239   msg(L_INFO, "Daemon is running");
240   msg(L_INFO, "uid=%d/%d gid=%d/%d", (int) getuid(), (int) geteuid(), (int) getgid(), (int) getegid());
241
242   struct sigaction sa_term = { .sa_handler = term_handler };
243   struct sigaction sa_hup = { .sa_handler = hup_handler };
244   if (sigaction(SIGTERM, &sa_term, NULL) < 0 ||
245       sigaction(SIGHUP, &sa_hup, NULL) < 0)
246     ASSERT(0);
247
248   while (!terminate)
249     {
250       if (!sleep(60))
251         {
252           msg(L_INFO, "Timeout elapsed, terminating in a while");
253           break;
254         }
255     }
256
257   sleep(2);
258   msg(L_INFO, "Daemon is shutting down");
259   daemon_exit(dp);
260 }
261
262 int main(int argc, char **argv)
263 {
264   struct daemon_params dp = {
265     .pid_file = "/tmp/123",
266   };
267
268   int opt;
269   while ((opt = getopt(argc, argv, "p:u:g:")) >= 0)
270     switch (opt)
271       {
272       case 'p':
273         dp.pid_file = optarg;
274         break;
275       case 'u':
276         dp.run_as_user = optarg;
277         break;
278       case 'g':
279         dp.run_as_group = optarg;
280         break;
281       default:
282         die("Invalid arguments");
283       }
284
285   daemon_init(&dp);
286   daemon_run(&dp, body);
287   msg(L_INFO, "Main program has ended");
288   return 0;
289 }
290
291 #endif