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