]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | /* | |
4 | * Ceph - scalable distributed file system | |
5 | * | |
6 | * Copyright (C) 2011 New Dream Network | |
7 | * | |
8 | * This is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU Lesser General Public | |
10 | * License version 2.1, as published by the Free Software | |
11 | * Foundation. See file COPYING. | |
12 | * | |
13 | */ | |
14 | ||
15 | #include "common/ceph_argparse.h" | |
16 | #include "common/code_environment.h" | |
17 | #include "common/config.h" | |
18 | #include "common/debug.h" | |
19 | #include "common/errno.h" | |
20 | #include "common/signal.h" | |
21 | #include "common/version.h" | |
22 | #include "erasure-code/ErasureCodePlugin.h" | |
23 | #include "global/global_context.h" | |
24 | #include "global/global_init.h" | |
25 | #include "global/pidfile.h" | |
26 | #include "global/signal_handler.h" | |
27 | #include "include/compat.h" | |
28 | #include "include/str_list.h" | |
29 | #include "common/admin_socket.h" | |
30 | ||
31 | #include <pwd.h> | |
32 | #include <grp.h> | |
33 | #include <errno.h> | |
34 | ||
35 | #ifdef HAVE_SYS_PRCTL_H | |
36 | #include <sys/prctl.h> | |
37 | #endif | |
38 | ||
39 | #define dout_context g_ceph_context | |
40 | #define dout_subsys ceph_subsys_ | |
41 | ||
42 | static void global_init_set_globals(CephContext *cct) | |
43 | { | |
44 | g_ceph_context = cct; | |
45 | g_conf = cct->_conf; | |
46 | } | |
47 | ||
48 | static void output_ceph_version() | |
49 | { | |
50 | char buf[1024]; | |
51 | snprintf(buf, sizeof(buf), "%s, process %s, pid %d", | |
52 | pretty_version_to_str().c_str(), | |
53 | get_process_name_cpp().c_str(), getpid()); | |
54 | generic_dout(0) << buf << dendl; | |
55 | } | |
56 | ||
57 | static const char* c_str_or_null(const std::string &str) | |
58 | { | |
59 | if (str.empty()) | |
60 | return NULL; | |
61 | return str.c_str(); | |
62 | } | |
63 | ||
64 | static int chown_path(const std::string &pathname, const uid_t owner, const gid_t group, | |
65 | const std::string &uid_str, const std::string &gid_str) | |
66 | { | |
67 | const char *pathname_cstr = c_str_or_null(pathname); | |
68 | ||
69 | if (!pathname_cstr) { | |
70 | return 0; | |
71 | } | |
72 | ||
73 | int r = ::chown(pathname_cstr, owner, group); | |
74 | ||
75 | if (r < 0) { | |
76 | r = -errno; | |
77 | cerr << "warning: unable to chown() " << pathname << " as " | |
78 | << uid_str << ":" << gid_str << ": " << cpp_strerror(r) << std::endl; | |
79 | } | |
80 | ||
81 | return r; | |
82 | } | |
83 | ||
84 | void global_pre_init(std::vector < const char * > *alt_def_args, | |
85 | std::vector < const char* >& args, | |
86 | uint32_t module_type, code_environment_t code_env, | |
b32b8144 | 87 | int flags) |
7c673cae FG |
88 | { |
89 | std::string conf_file_list; | |
90 | std::string cluster = ""; | |
91 | CephInitParameters iparams = ceph_argparse_early_args(args, module_type, | |
92 | &cluster, &conf_file_list); | |
b32b8144 | 93 | CephContext *cct = common_preinit(iparams, code_env, flags); |
7c673cae FG |
94 | cct->_conf->cluster = cluster; |
95 | global_init_set_globals(cct); | |
96 | md_config_t *conf = cct->_conf; | |
97 | ||
98 | if (alt_def_args) | |
99 | conf->parse_argv(*alt_def_args); // alternative default args | |
100 | ||
101 | int ret = conf->parse_config_files(c_str_or_null(conf_file_list), | |
102 | &cerr, flags); | |
103 | if (ret == -EDOM) { | |
104 | dout_emergency("global_init: error parsing config file.\n"); | |
105 | _exit(1); | |
106 | } | |
107 | else if (ret == -ENOENT) { | |
108 | if (!(flags & CINIT_FLAG_NO_DEFAULT_CONFIG_FILE)) { | |
109 | if (conf_file_list.length()) { | |
110 | ostringstream oss; | |
111 | oss << "global_init: unable to open config file from search list " | |
112 | << conf_file_list << "\n"; | |
113 | dout_emergency(oss.str()); | |
114 | _exit(1); | |
115 | } else { | |
224ce89b | 116 | derr << "did not load config file, using default settings." << dendl; |
7c673cae FG |
117 | } |
118 | } | |
119 | } | |
120 | else if (ret) { | |
121 | dout_emergency("global_init: error reading config file.\n"); | |
122 | _exit(1); | |
123 | } | |
124 | ||
125 | conf->parse_env(); // environment variables override | |
126 | ||
127 | conf->parse_argv(args); // argv override | |
128 | ||
129 | // Now we're ready to complain about config file parse errors | |
130 | g_conf->complain_about_parse_errors(g_ceph_context); | |
131 | } | |
132 | ||
133 | boost::intrusive_ptr<CephContext> | |
134 | global_init(std::vector < const char * > *alt_def_args, | |
135 | std::vector < const char* >& args, | |
136 | uint32_t module_type, code_environment_t code_env, | |
137 | int flags, | |
138 | const char *data_dir_option, bool run_pre_init) | |
139 | { | |
140 | // Ensure we're not calling the global init functions multiple times. | |
141 | static bool first_run = true; | |
142 | if (run_pre_init) { | |
143 | // We will run pre_init from here (default). | |
144 | assert(!g_ceph_context && first_run); | |
145 | global_pre_init(alt_def_args, args, module_type, code_env, flags); | |
146 | } else { | |
147 | // Caller should have invoked pre_init manually. | |
148 | assert(g_ceph_context && first_run); | |
149 | } | |
150 | first_run = false; | |
151 | ||
152 | // Verify flags have not changed if global_pre_init() has been called | |
153 | // manually. If they have, update them. | |
154 | if (g_ceph_context->get_init_flags() != flags) { | |
155 | g_ceph_context->set_init_flags(flags); | |
156 | } | |
157 | ||
158 | // signal stuff | |
159 | int siglist[] = { SIGPIPE, 0 }; | |
160 | block_signals(siglist, NULL); | |
161 | ||
162 | if (g_conf->fatal_signal_handlers) | |
163 | install_standard_sighandlers(); | |
164 | ||
165 | if (g_conf->log_flush_on_exit) | |
166 | g_ceph_context->_log->set_flush_on_exit(); | |
167 | ||
168 | // drop privileges? | |
169 | ostringstream priv_ss; | |
170 | ||
171 | // consider --setuser root a no-op, even if we're not root | |
172 | if (getuid() != 0) { | |
173 | if (g_conf->setuser.length()) { | |
174 | cerr << "ignoring --setuser " << g_conf->setuser << " since I am not root" | |
175 | << std::endl; | |
176 | } | |
177 | if (g_conf->setgroup.length()) { | |
178 | cerr << "ignoring --setgroup " << g_conf->setgroup | |
179 | << " since I am not root" << std::endl; | |
180 | } | |
181 | } else if (g_conf->setgroup.length() || | |
182 | g_conf->setuser.length()) { | |
183 | uid_t uid = 0; // zero means no change; we can only drop privs here. | |
184 | gid_t gid = 0; | |
185 | std::string uid_string; | |
186 | std::string gid_string; | |
187 | if (g_conf->setuser.length()) { | |
188 | uid = atoi(g_conf->setuser.c_str()); | |
189 | if (!uid) { | |
190 | char buf[4096]; | |
191 | struct passwd pa; | |
192 | struct passwd *p = 0; | |
193 | getpwnam_r(g_conf->setuser.c_str(), &pa, buf, sizeof(buf), &p); | |
194 | if (!p) { | |
195 | cerr << "unable to look up user '" << g_conf->setuser << "'" | |
196 | << std::endl; | |
197 | exit(1); | |
198 | } | |
199 | uid = p->pw_uid; | |
200 | gid = p->pw_gid; | |
201 | uid_string = g_conf->setuser; | |
202 | } | |
203 | } | |
204 | if (g_conf->setgroup.length() > 0) { | |
205 | gid = atoi(g_conf->setgroup.c_str()); | |
206 | if (!gid) { | |
207 | char buf[4096]; | |
208 | struct group gr; | |
209 | struct group *g = 0; | |
210 | getgrnam_r(g_conf->setgroup.c_str(), &gr, buf, sizeof(buf), &g); | |
211 | if (!g) { | |
212 | cerr << "unable to look up group '" << g_conf->setgroup << "'" | |
213 | << ": " << cpp_strerror(errno) << std::endl; | |
214 | exit(1); | |
215 | } | |
216 | gid = g->gr_gid; | |
217 | gid_string = g_conf->setgroup; | |
218 | } | |
219 | } | |
220 | if ((uid || gid) && | |
221 | g_conf->setuser_match_path.length()) { | |
222 | // induce early expansion of setuser_match_path config option | |
223 | string match_path = g_conf->setuser_match_path; | |
224 | g_conf->early_expand_meta(match_path, &cerr); | |
225 | struct stat st; | |
226 | int r = ::stat(match_path.c_str(), &st); | |
227 | if (r < 0) { | |
228 | cerr << "unable to stat setuser_match_path " | |
229 | << g_conf->setuser_match_path | |
230 | << ": " << cpp_strerror(errno) << std::endl; | |
231 | exit(1); | |
232 | } | |
233 | if ((uid && uid != st.st_uid) || | |
234 | (gid && gid != st.st_gid)) { | |
235 | cerr << "WARNING: will not setuid/gid: " << match_path | |
236 | << " owned by " << st.st_uid << ":" << st.st_gid | |
237 | << " and not requested " << uid << ":" << gid | |
238 | << std::endl; | |
239 | uid = 0; | |
240 | gid = 0; | |
241 | uid_string.erase(); | |
242 | gid_string.erase(); | |
243 | } else { | |
244 | priv_ss << "setuser_match_path " | |
245 | << match_path << " owned by " | |
246 | << st.st_uid << ":" << st.st_gid << ". "; | |
247 | } | |
248 | } | |
249 | g_ceph_context->set_uid_gid(uid, gid); | |
250 | g_ceph_context->set_uid_gid_strings(uid_string, gid_string); | |
251 | if ((flags & CINIT_FLAG_DEFER_DROP_PRIVILEGES) == 0) { | |
252 | if (setgid(gid) != 0) { | |
253 | cerr << "unable to setgid " << gid << ": " << cpp_strerror(errno) | |
254 | << std::endl; | |
255 | exit(1); | |
256 | } | |
257 | if (setuid(uid) != 0) { | |
258 | cerr << "unable to setuid " << uid << ": " << cpp_strerror(errno) | |
259 | << std::endl; | |
260 | exit(1); | |
261 | } | |
262 | priv_ss << "set uid:gid to " << uid << ":" << gid << " (" << uid_string << ":" << gid_string << ")"; | |
263 | } else { | |
264 | priv_ss << "deferred set uid:gid to " << uid << ":" << gid << " (" << uid_string << ":" << gid_string << ")"; | |
265 | } | |
266 | } | |
267 | ||
268 | #if defined(HAVE_SYS_PRCTL_H) | |
269 | if (prctl(PR_SET_DUMPABLE, 1) == -1) { | |
270 | cerr << "warning: unable to set dumpable flag: " << cpp_strerror(errno) << std::endl; | |
271 | } | |
272 | #endif | |
273 | ||
274 | // Expand metavariables. Invoke configuration observers. Open log file. | |
275 | g_conf->apply_changes(NULL); | |
276 | ||
277 | if (g_conf->run_dir.length() && | |
278 | code_env == CODE_ENVIRONMENT_DAEMON && | |
279 | !(flags & CINIT_FLAG_NO_DAEMON_ACTIONS)) { | |
280 | int r = ::mkdir(g_conf->run_dir.c_str(), 0755); | |
281 | if (r < 0 && errno != EEXIST) { | |
282 | cerr << "warning: unable to create " << g_conf->run_dir << ": " << cpp_strerror(errno) << std::endl; | |
283 | } | |
284 | } | |
285 | ||
286 | register_assert_context(g_ceph_context); | |
287 | ||
288 | // call all observers now. this has the side-effect of configuring | |
289 | // and opening the log file immediately. | |
290 | g_conf->call_all_observers(); | |
291 | ||
292 | if (priv_ss.str().length()) { | |
293 | dout(0) << priv_ss.str() << dendl; | |
294 | } | |
295 | ||
296 | if ((flags & CINIT_FLAG_DEFER_DROP_PRIVILEGES) && | |
297 | (g_ceph_context->get_set_uid() || g_ceph_context->get_set_gid())) { | |
298 | // Fix ownership on log files and run directories if needed. | |
299 | // Admin socket files are chown()'d during the common init path _after_ | |
300 | // the service thread has been started. This is sadly a bit of a hack :( | |
301 | chown_path(g_conf->run_dir, | |
302 | g_ceph_context->get_set_uid(), | |
303 | g_ceph_context->get_set_gid(), | |
304 | g_ceph_context->get_set_uid_string(), | |
305 | g_ceph_context->get_set_gid_string()); | |
306 | g_ceph_context->_log->chown_log_file( | |
307 | g_ceph_context->get_set_uid(), | |
308 | g_ceph_context->get_set_gid()); | |
309 | } | |
310 | ||
311 | // Now we're ready to complain about config file parse errors | |
312 | g_conf->complain_about_parse_errors(g_ceph_context); | |
313 | ||
314 | // test leak checking | |
315 | if (g_conf->debug_deliberately_leak_memory) { | |
316 | derr << "deliberately leaking some memory" << dendl; | |
317 | char *s = new char[1234567]; | |
318 | (void)s; | |
319 | // cppcheck-suppress memleak | |
320 | } | |
321 | ||
322 | if (code_env == CODE_ENVIRONMENT_DAEMON && !(flags & CINIT_FLAG_NO_DAEMON_ACTIONS)) | |
323 | output_ceph_version(); | |
324 | ||
325 | if (g_ceph_context->crush_location.init_on_startup()) { | |
326 | cerr << " failed to init_on_startup : " << cpp_strerror(errno) << std::endl; | |
327 | exit(1); | |
328 | } | |
329 | ||
330 | return boost::intrusive_ptr<CephContext>{g_ceph_context, false}; | |
331 | } | |
332 | ||
333 | void intrusive_ptr_add_ref(CephContext* cct) | |
334 | { | |
335 | cct->get(); | |
336 | } | |
337 | ||
338 | void intrusive_ptr_release(CephContext* cct) | |
339 | { | |
340 | cct->put(); | |
341 | } | |
342 | ||
343 | void global_print_banner(void) | |
344 | { | |
345 | output_ceph_version(); | |
346 | } | |
347 | ||
348 | int global_init_prefork(CephContext *cct) | |
349 | { | |
350 | if (g_code_env != CODE_ENVIRONMENT_DAEMON) | |
351 | return -1; | |
352 | ||
353 | const md_config_t *conf = cct->_conf; | |
354 | if (!conf->daemonize) { | |
355 | ||
356 | if (pidfile_write(conf) < 0) | |
357 | exit(1); | |
358 | ||
359 | if ((cct->get_init_flags() & CINIT_FLAG_DEFER_DROP_PRIVILEGES) && | |
360 | (cct->get_set_uid() || cct->get_set_gid())) { | |
361 | chown_path(conf->pid_file, cct->get_set_uid(), cct->get_set_gid(), | |
362 | cct->get_set_uid_string(), cct->get_set_gid_string()); | |
363 | } | |
364 | ||
365 | return -1; | |
366 | } | |
367 | ||
368 | cct->notify_pre_fork(); | |
369 | // stop log thread | |
370 | cct->_log->flush(); | |
371 | cct->_log->stop(); | |
372 | return 0; | |
373 | } | |
374 | ||
375 | void global_init_daemonize(CephContext *cct) | |
376 | { | |
377 | if (global_init_prefork(cct) < 0) | |
378 | return; | |
379 | ||
380 | #if !defined(_AIX) | |
381 | int ret = daemon(1, 1); | |
382 | if (ret) { | |
383 | ret = errno; | |
384 | derr << "global_init_daemonize: BUG: daemon error: " | |
385 | << cpp_strerror(ret) << dendl; | |
386 | exit(1); | |
387 | } | |
388 | ||
389 | global_init_postfork_start(cct); | |
390 | global_init_postfork_finish(cct); | |
391 | #else | |
392 | # warning daemon not supported on aix | |
393 | #endif | |
394 | } | |
395 | ||
396 | void global_init_postfork_start(CephContext *cct) | |
397 | { | |
398 | // restart log thread | |
399 | cct->_log->start(); | |
400 | cct->notify_post_fork(); | |
401 | ||
402 | /* This is the old trick where we make file descriptors 0, 1, and possibly 2 | |
403 | * point to /dev/null. | |
404 | * | |
405 | * We have to do this because otherwise some arbitrary call to open() later | |
406 | * in the program might get back one of these file descriptors. It's hard to | |
407 | * guarantee that nobody ever writes to stdout, even though they're not | |
408 | * supposed to. | |
409 | */ | |
410 | VOID_TEMP_FAILURE_RETRY(close(STDIN_FILENO)); | |
411 | if (open("/dev/null", O_RDONLY) < 0) { | |
412 | int err = errno; | |
413 | derr << "global_init_daemonize: open(/dev/null) failed: error " | |
414 | << err << dendl; | |
415 | exit(1); | |
416 | } | |
7c673cae FG |
417 | |
418 | const md_config_t *conf = cct->_conf; | |
419 | if (pidfile_write(conf) < 0) | |
420 | exit(1); | |
421 | ||
422 | if ((cct->get_init_flags() & CINIT_FLAG_DEFER_DROP_PRIVILEGES) && | |
423 | (cct->get_set_uid() || cct->get_set_gid())) { | |
424 | chown_path(conf->pid_file, cct->get_set_uid(), cct->get_set_gid(), | |
425 | cct->get_set_uid_string(), cct->get_set_gid_string()); | |
426 | } | |
427 | } | |
428 | ||
429 | void global_init_postfork_finish(CephContext *cct) | |
430 | { | |
28e407b8 AA |
431 | /* We only close stdout+stderr once the caller decides the daemonization |
432 | * process is finished. This way we can allow error or other messages to be | |
7c673cae FG |
433 | * propagated in a manner that the user is able to see. |
434 | */ | |
435 | if (!(cct->get_init_flags() & CINIT_FLAG_NO_CLOSE_STDERR)) { | |
436 | int ret = global_init_shutdown_stderr(cct); | |
437 | if (ret) { | |
438 | derr << "global_init_daemonize: global_init_shutdown_stderr failed with " | |
439 | << "error code " << ret << dendl; | |
440 | exit(1); | |
441 | } | |
442 | } | |
28e407b8 AA |
443 | |
444 | VOID_TEMP_FAILURE_RETRY(close(STDOUT_FILENO)); | |
445 | if (open("/dev/null", O_RDONLY) < 0) { | |
446 | int err = errno; | |
447 | derr << "global_init_daemonize: open(/dev/null) failed: error " | |
448 | << err << dendl; | |
449 | exit(1); | |
450 | } | |
451 | ||
7c673cae FG |
452 | ldout(cct, 1) << "finished global_init_daemonize" << dendl; |
453 | } | |
454 | ||
455 | ||
456 | void global_init_chdir(const CephContext *cct) | |
457 | { | |
458 | const md_config_t *conf = cct->_conf; | |
459 | if (conf->chdir.empty()) | |
460 | return; | |
461 | if (::chdir(conf->chdir.c_str())) { | |
462 | int err = errno; | |
463 | derr << "global_init_chdir: failed to chdir to directory: '" | |
464 | << conf->chdir << "': " << cpp_strerror(err) << dendl; | |
465 | } | |
466 | } | |
467 | ||
468 | /* Map stderr to /dev/null. This isn't really re-entrant; we rely on the old unix | |
469 | * behavior that the file descriptor that gets assigned is the lowest | |
470 | * available one. | |
471 | */ | |
472 | int global_init_shutdown_stderr(CephContext *cct) | |
473 | { | |
474 | VOID_TEMP_FAILURE_RETRY(close(STDERR_FILENO)); | |
475 | if (open("/dev/null", O_RDONLY) < 0) { | |
476 | int err = errno; | |
477 | derr << "global_init_shutdown_stderr: open(/dev/null) failed: error " | |
478 | << err << dendl; | |
479 | return 1; | |
480 | } | |
481 | cct->_log->set_stderr_level(-1, -1); | |
482 | return 0; | |
483 | } | |
484 | ||
485 | int global_init_preload_erasure_code(const CephContext *cct) | |
486 | { | |
487 | const md_config_t *conf = cct->_conf; | |
488 | string plugins = conf->osd_erasure_code_plugins; | |
489 | ||
490 | // validate that this is a not a legacy plugin | |
491 | list<string> plugins_list; | |
492 | get_str_list(plugins, plugins_list); | |
493 | for (list<string>::iterator i = plugins_list.begin(); | |
494 | i != plugins_list.end(); | |
495 | ++i) { | |
496 | string plugin_name = *i; | |
497 | string replacement = ""; | |
498 | ||
499 | if (plugin_name == "jerasure_generic" || | |
500 | plugin_name == "jerasure_sse3" || | |
501 | plugin_name == "jerasure_sse4" || | |
502 | plugin_name == "jerasure_neon") { | |
503 | replacement = "jerasure"; | |
504 | } | |
505 | else if (plugin_name == "shec_generic" || | |
506 | plugin_name == "shec_sse3" || | |
507 | plugin_name == "shec_sse4" || | |
508 | plugin_name == "shec_neon") { | |
509 | replacement = "shec"; | |
510 | } | |
511 | ||
512 | if (replacement != "") { | |
513 | dout(0) << "WARNING: osd_erasure_code_plugins contains plugin " | |
514 | << plugin_name << " that is now deprecated. Please modify the value " | |
515 | << "for osd_erasure_code_plugins to use " << replacement << " instead." << dendl; | |
516 | } | |
517 | } | |
518 | ||
519 | stringstream ss; | |
520 | int r = ErasureCodePluginRegistry::instance().preload( | |
521 | plugins, | |
522 | conf->get_val<std::string>("erasure_code_dir"), | |
523 | &ss); | |
524 | if (r) | |
525 | derr << ss.str() << dendl; | |
526 | else | |
527 | dout(0) << ss.str() << dendl; | |
528 | return r; | |
529 | } |