1 /*
2 * Copyright 2003-2021 The Music Player Daemon Project
3 * http://www.musicpd.org
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "config.h"
21 #include "Daemon.hxx"
22 #include "system/Error.hxx"
23 #include "fs/AllocatedPath.hxx"
24 #include "util/RuntimeError.hxx"
25
26 #ifndef _WIN32
27 #include "PidFile.hxx"
28 #endif
29
30 #include <fcntl.h>
31
32 #ifndef _WIN32
33 #include <csignal>
34 #include <sys/wait.h>
35 #include <pwd.h>
36 #include <grp.h>
37 #endif
38
39 #ifndef WCOREDUMP
40 #define WCOREDUMP(v) 0
41 #endif
42
43 #ifndef _WIN32
44
45 /** the Unix user name which MPD runs as */
46 static char *user_name;
47
48 /** the Unix user id which MPD runs as */
49 static uid_t user_uid = (uid_t)-1;
50
51 /** the Unix group id which MPD runs as */
52 static gid_t user_gid = (gid_t)-1;
53
54 /** the absolute path of the pidfile */
55 static AllocatedPath pidfile = nullptr;
56
57 /* whether "group" conf. option was given */
58 static bool had_group = false;
59
60 /**
61 * The write end of a pipe that is used to notify the parent process
62 * that initialization has finished and that it should detach.
63 */
64 static int detach_fd = -1;
65
66 void
daemonize_kill()67 daemonize_kill()
68 {
69 if (pidfile.IsNull())
70 throw std::runtime_error("no pid_file specified in the config file");
71
72 const pid_t pid = ReadPidFile(pidfile);
73 if (pid < 0) {
74 const std::string utf8 = pidfile.ToUTF8();
75 throw FormatErrno("unable to read the pid from file \"%s\"",
76 utf8.c_str());
77 }
78
79 if (kill(pid, SIGTERM) < 0)
80 throw FormatErrno("unable to kill process %i", int(pid));
81
82 std::exit(EXIT_SUCCESS);
83 }
84
85 void
daemonize_close_stdin()86 daemonize_close_stdin()
87 {
88 close(STDIN_FILENO);
89 open("/dev/null", O_RDONLY);
90 }
91
92 void
daemonize_set_user()93 daemonize_set_user()
94 {
95 if (user_name == nullptr)
96 return;
97
98 /* set gid */
99 if (user_gid != (gid_t)-1 && user_gid != getgid() &&
100 setgid(user_gid) == -1) {
101 throw FormatErrno("Failed to set group %d", (int)user_gid);
102 }
103
104 #ifdef HAVE_INITGROUPS
105 /* init supplementary groups
106 * (must be done before we change our uid)
107 */
108 if (!had_group &&
109 /* no need to set the new user's supplementary groups if
110 we are already this user */
111 user_uid != getuid() &&
112 initgroups(user_name, user_gid) == -1) {
113 throw FormatErrno("Failed to set supplementary groups "
114 "of user \"%s\"",
115 user_name);
116 }
117 #endif
118
119 /* set uid */
120 if (user_uid != (uid_t)-1 && user_uid != getuid() &&
121 setuid(user_uid) == -1) {
122 throw FormatErrno("Failed to set user \"%s\"", user_name);
123 }
124 }
125
126 void
daemonize_begin(bool detach)127 daemonize_begin(bool detach)
128 {
129 /* release the current working directory */
130 if (chdir("/") < 0)
131 throw MakeErrno("problems changing to root directory");
132
133 if (!detach)
134 /* the rest of this function deals with detaching the
135 process */
136 return;
137
138 /* do this before daemonizing so we can fail gracefully if we
139 can't write to the pid file */
140 PidFile pidfile2(pidfile);
141
142 /* flush all file handles before duplicating the buffers */
143
144 fflush(nullptr);
145
146 /* create a pipe to synchronize the parent and the child */
147
148 int fds[2];
149 if (pipe(fds) < 0)
150 throw MakeErrno("pipe() failed");
151
152 /* move to a child process */
153
154 pid_t pid = fork();
155 if (pid < 0)
156 throw MakeErrno("fork() failed");
157
158 if (pid == 0) {
159 /* in the child process */
160
161 pidfile2.Close();
162 close(fds[0]);
163 detach_fd = fds[1];
164
165 /* detach from the current session */
166 setsid();
167
168 /* continue starting MPD */
169 return;
170 }
171
172 /* in the parent process */
173
174 close(fds[1]);
175
176 int result;
177 ssize_t nbytes = read(fds[0], &result, sizeof(result));
178 if (nbytes == (ssize_t)sizeof(result)) {
179 /* the child process was successful */
180 pidfile2.Write(pid);
181 std::exit(EXIT_SUCCESS);
182 }
183
184 /* something bad happened in the child process */
185
186 pidfile2.Delete(pidfile);
187
188 int status;
189 pid_t pid2 = waitpid(pid, &status, 0);
190 if (pid2 < 0)
191 throw MakeErrno("waitpid() failed");
192
193 if (WIFSIGNALED(status))
194 throw FormatErrno("MPD died from signal %d%s", WTERMSIG(status),
195 WCOREDUMP(status) ? " (core dumped)" : "");
196
197 std::exit(WEXITSTATUS(status));
198 }
199
200 void
daemonize_commit()201 daemonize_commit()
202 {
203 if (detach_fd >= 0) {
204 /* tell the parent process to let go of us and exit
205 indicating success */
206 int result = 0;
207 write(detach_fd, &result, sizeof(result));
208 close(detach_fd);
209 } else
210 /* the pidfile was not written by the parent because
211 there is no parent - do it now */
212 PidFile(pidfile).Write();
213 }
214
215 void
daemonize_init(const char * user,const char * group,AllocatedPath && _pidfile)216 daemonize_init(const char *user, const char *group, AllocatedPath &&_pidfile)
217 {
218 if (user) {
219 struct passwd *pwd = getpwnam(user);
220 if (pwd == nullptr)
221 throw FormatRuntimeError("no such user \"%s\"", user);
222
223 user_uid = pwd->pw_uid;
224 user_gid = pwd->pw_gid;
225
226 user_name = strdup(user);
227
228 /* this is needed by libs such as arts */
229 setenv("HOME", pwd->pw_dir, true);
230 }
231
232 if (group) {
233 struct group *grp = getgrnam(group);
234 if (grp == nullptr)
235 throw FormatRuntimeError("no such group \"%s\"",
236 group);
237 user_gid = grp->gr_gid;
238 had_group = true;
239 }
240
241
242 pidfile = std::move(_pidfile);
243 }
244
245 void
daemonize_finish()246 daemonize_finish()
247 {
248 if (!pidfile.IsNull()) {
249 unlink(pidfile.c_str());
250 pidfile = nullptr;
251 }
252
253 free(user_name);
254 }
255
256 #endif
257