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