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