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