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