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