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