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