Branch data Line data Source code
1 : : #include <base/phdr_cache.h>
2 : : #include <base/scope_guard.h>
3 : : #include <base/defines.h>
4 : :
5 : : #include <Common/EnvironmentChecks.h>
6 : : #include <Common/Exception.h>
7 : : #include <Common/StringUtils.h>
8 : : #include <Common/getHashOfLoadedBinary.h>
9 : : #include <Common/Crypto/OpenSSLInitializer.h>
10 : :
11 : :
12 : : #include "config.h"
13 : : #include "config_tools.h"
14 : :
15 : : #include <unistd.h>
16 : :
17 : : #include <filesystem>
18 : : #include <iostream>
19 : : #include <new>
20 : : #include <string>
21 : : #include <string_view>
22 : : #include <utility> /// pair
23 : : #include <vector>
24 : :
25 : : #ifdef SANITIZER
26 : : #pragma clang diagnostic push
27 : : #pragma clang diagnostic ignored "-Wreserved-identifier"
28 : : extern "C" {
29 : : #ifdef ADDRESS_SANITIZER
30 : : const char * __asan_default_options();
31 : : const char * __lsan_default_options();
32 : : const char * __lsan_default_suppressions();
33 : : const char * __asan_default_options()
34 : : {
35 : : return "halt_on_error=1 abort_on_error=1";
36 : : }
37 : : const char * __lsan_default_options()
38 : : {
39 : : return "max_allocation_size_mb=32768";
40 : : }
41 : : const char * __lsan_default_suppressions()
42 : : {
43 : : /// OpenSSL intentionally does not free all global state at exit.
44 : : /// These are known false positives from OpenSSL provider and EVP initialization.
45 : : return "leak:ossl_provider_new\n"
46 : : "leak:OSSL_PROVIDER_try_load_ex\n"
47 : : "leak:ossl_rand_ctx_new\n"
48 : : "leak:OSSL_LIB_CTX_new\n"
49 : : "leak:ossl_legacy_provider_init\n"
50 : : /// OpenSSL EVP method objects are cached globally and never freed at exit.
51 : : /// Triggered when S3 client initializes HMAC (AWS SDK -> OpenSSL HMAC_Init_ex).
52 : : "leak:evp_md_new\n"
53 : : "leak:construct_evp_method\n"
54 : : "leak:CRYPTO_THREAD_lock_new\n";
55 : : }
56 : : #endif
57 : :
58 : : #ifdef MEMORY_SANITIZER
59 : : const char * __msan_default_options();
60 : : const char * __msan_default_options()
61 : : {
62 : : return "abort_on_error=1 poison_in_dtor=1 max_allocation_size_mb=32768";
63 : : }
64 : : #endif
65 : :
66 : : #ifdef THREAD_SANITIZER
67 : : const char * __tsan_default_options();
68 : : const char * __tsan_default_options()
69 : : {
70 : : return "halt_on_error=1 abort_on_error=1 history_size=7 second_deadlock_stack=1 max_allocation_size_mb=32768";
71 : : }
72 : : #endif
73 : :
74 : : #ifdef UNDEFINED_BEHAVIOR_SANITIZER
75 : : const char * __ubsan_default_options();
76 : : const char * __ubsan_default_options()
77 : : {
78 : : return "print_stacktrace=1 max_allocation_size_mb=32768";
79 : : }
80 : : #endif
81 : : }
82 : : #pragma clang diagnostic pop
83 : : #endif
84 : :
85 : : /// Universal executable for various clickhouse applications
86 : : int mainEntryClickHouseBenchmark(int argc, char ** argv);
87 : : int mainEntryClickHouseCheckMarks(int argc, char ** argv);
88 : : int mainEntryClickHouseChecksumForCompressedBlock(int, char **);
89 : : int mainEntryClickHouseClient(int argc, char ** argv);
90 : : int mainEntryClickHouseCompressor(int argc, char ** argv);
91 : : int mainEntryClickHouseDisks(int argc, char ** argv);
92 : : int mainEntryClickHouseExtractFromConfig(int argc, char ** argv);
93 : : int mainEntryClickHouseFormat(int argc, char ** argv);
94 : : int mainEntryClickHouseFstDumpTree(int argc, char ** argv);
95 : : int mainEntryClickHouseGitImport(int argc, char ** argv);
96 : : int mainEntryClickHouseLocal(int argc, char ** argv);
97 : : int mainEntryClickHouseObfuscator(int argc, char ** argv);
98 : : int mainEntryClickHouseSU(int argc, char ** argv);
99 : : int mainEntryClickHouseDockerInit(int argc, char ** argv);
100 : : int mainEntryClickHouseServer(int argc, char ** argv);
101 : : int mainEntryClickHouseStaticFilesDiskUploader(int argc, char ** argv);
102 : : int mainEntryClickHouseZooKeeperDumpTree(int argc, char ** argv);
103 : : int mainEntryClickHouseZooKeeperRemoveByList(int argc, char ** argv);
104 : :
105 : : int mainEntryClickHouseHashBinary(int argc, char ** argv);
106 : : int mainEntryClickHouseHashBinary(int argc, char ** argv)
107 : 4 : {
108 [ + - ][ + - ]: 4 : if (argc > 1 && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0))
[ # # ]
109 : 4 : {
110 : 4 : std::cout << "Usage: clickhouse hash-binary\n"
111 : 4 : "Prints hash of ClickHouse binary.\n"
112 : 4 : " -h, --help Print this message\n"
113 : 4 : "Result is intentionally without newline. So you can run:\n"
114 : 4 : "objcopy --add-section .clickhouse.hash=<(./clickhouse hash-binary) clickhouse\n\n"
115 : 4 : "Current binary hash: ";
116 : 4 : }
117 : 4 : std::cout << getHashOfLoadedBinaryHex();
118 : 4 : return 0;
119 : 4 : }
120 : :
121 : : #if ENABLE_CLICKHOUSE_KEEPER
122 : : int mainEntryClickHouseKeeper(int argc, char ** argv);
123 : : #endif
124 : : #if ENABLE_CLICKHOUSE_KEEPER_CONVERTER
125 : : int mainEntryClickHouseKeeperConverter(int argc, char ** argv);
126 : : #endif
127 : : #if ENABLE_CLICKHOUSE_KEEPER_CLIENT
128 : : int mainEntryClickHouseKeeperClient(int argc, char ** argv);
129 : : #endif
130 : : #if USE_RAPIDJSON && USE_NURAFT
131 : : int mainEntryClickHouseKeeperBench(int argc, char ** argv);
132 : : #endif
133 : : #if USE_NURAFT
134 : : int mainEntryClickHouseKeeperDataDumper(int argc, char ** argv);
135 : : int mainEntryClickHouseKeeperUtils(int argc, char ** argv);
136 : : #endif
137 : :
138 : : #if USE_CHDIG
139 : : extern "C" int chdig_main(int argc, char ** argv);
140 : : int mainEntryClickHouseChdig(int argc, char ** argv);
141 : : int mainEntryClickHouseChdig(int argc, char ** argv)
142 : 4 : {
143 : 4 : return chdig_main(argc, argv);
144 : 4 : }
145 : : #endif
146 : :
147 : : // install
148 : : int mainEntryClickHouseInstall(int argc, char ** argv);
149 : : int mainEntryClickHouseStart(int argc, char ** argv);
150 : : int mainEntryClickHouseStop(int argc, char ** argv);
151 : : int mainEntryClickHouseStatus(int argc, char ** argv);
152 : : int mainEntryClickHouseRestart(int argc, char ** argv);
153 : :
154 : : /// Private-only programs
155 : : #if CLICKHOUSE_CLOUD
156 : : int mainEntryClickHouseSharedCatalogUtil(int argc, char ** argv);
157 : : #if ENABLE_DISTRIBUTED_CACHE
158 : : int mainEntryClickHouseDistributedCache(int argc, char ** argv);
159 : : #endif
160 : : int mainEntryClickHouseSharedMergeTreeGarbageCleaner(int argc, char ** argv);
161 : : int mainEntryClickHouseClearZooKeeperLocks(int argc, char ** argv);
162 : : int mainEntryClickHousePackedIO(int argc, char ** argv);
163 : : int mainEntryClickHouseMangler(int argc, char ** argv);
164 : : #endif
165 : :
166 : : namespace
167 : : {
168 : :
169 : : using MainFunc = int (*)(int, char**);
170 : :
171 : : /// Forward declaration, since clickhouse_applications is defined after this function.
172 : : void printHelp(std::ostream & out);
173 : :
174 : : int mainEntryHelp(int, char **)
175 : 28 : {
176 : 28 : printHelp(std::cout);
177 : 28 : return 0;
178 : 28 : }
179 : :
180 : : int printHelpOnError(int, char **)
181 : 12 : {
182 : 12 : printHelp(std::cerr);
183 : 12 : return -1;
184 : 12 : }
185 : :
186 : : /// Add an item here to register new application.
187 : : /// This list has a "priority" - e.g. we need to disambiguate clickhouse --format being
188 : : /// either clickouse-format or clickhouse-{local, client} --format.
189 : : /// Currently we will prefer the latter option.
190 : : std::pair<std::string_view, MainFunc> clickhouse_applications[] =
191 : : {
192 : : {"local", mainEntryClickHouseLocal},
193 : : {"client", mainEntryClickHouseClient},
194 : : #if USE_CHDIG
195 : : {"chdig", mainEntryClickHouseChdig},
196 : : {"dig", mainEntryClickHouseChdig},
197 : : #endif
198 : : {"benchmark", mainEntryClickHouseBenchmark},
199 : : {"server", mainEntryClickHouseServer},
200 : : {"extract-from-config", mainEntryClickHouseExtractFromConfig},
201 : : {"compressor", mainEntryClickHouseCompressor},
202 : : {"format", mainEntryClickHouseFormat},
203 : : {"obfuscator", mainEntryClickHouseObfuscator},
204 : : {"git-import", mainEntryClickHouseGitImport},
205 : : {"static-files-disk-uploader", mainEntryClickHouseStaticFilesDiskUploader},
206 : : {"su", mainEntryClickHouseSU},
207 : : {"docker-init", mainEntryClickHouseDockerInit},
208 : : {"hash-binary", mainEntryClickHouseHashBinary},
209 : : {"disks", mainEntryClickHouseDisks},
210 : : {"check-marks", mainEntryClickHouseCheckMarks},
211 : : {"checksum-for-compressed-block", mainEntryClickHouseChecksumForCompressedBlock},
212 : : {"zookeeper-dump-tree", mainEntryClickHouseZooKeeperDumpTree},
213 : : {"zookeeper-remove-by-list", mainEntryClickHouseZooKeeperRemoveByList},
214 : :
215 : : // keeper
216 : : #if ENABLE_CLICKHOUSE_KEEPER
217 : : {"keeper", mainEntryClickHouseKeeper},
218 : : #endif
219 : : #if ENABLE_CLICKHOUSE_KEEPER_CONVERTER
220 : : {"keeper-converter", mainEntryClickHouseKeeperConverter},
221 : : #endif
222 : : #if ENABLE_CLICKHOUSE_KEEPER_CLIENT
223 : : {"keeper-client", mainEntryClickHouseKeeperClient},
224 : : #endif
225 : : #if USE_RAPIDJSON && USE_NURAFT
226 : : {"keeper-bench", mainEntryClickHouseKeeperBench},
227 : : #endif
228 : : #if USE_NURAFT
229 : : {"keeper-data-dumper", mainEntryClickHouseKeeperDataDumper},
230 : : {"keeper-utils", mainEntryClickHouseKeeperUtils},
231 : : #endif
232 : : // install
233 : : {"install", mainEntryClickHouseInstall},
234 : : {"start", mainEntryClickHouseStart},
235 : : {"stop", mainEntryClickHouseStop},
236 : : {"status", mainEntryClickHouseStatus},
237 : : {"restart", mainEntryClickHouseRestart},
238 : : // help
239 : : {"help", mainEntryHelp},
240 : :
241 : : /// Private-only programs
242 : : #if CLICKHOUSE_CLOUD
243 : : {"shared-merge-tree-garbage-cleaner", mainEntryClickHouseSharedMergeTreeGarbageCleaner},
244 : : {"clear-zookeeper-locks", mainEntryClickHouseClearZooKeeperLocks},
245 : : {"shared-catalog-util", mainEntryClickHouseSharedCatalogUtil},
246 : : {"packed-io", mainEntryClickHousePackedIO},
247 : : {"mangler", mainEntryClickHouseMangler},
248 : : #if ENABLE_DISTRIBUTED_CACHE
249 : : {"distributed-cache", mainEntryClickHouseDistributedCache}
250 : : #endif
251 : : #endif
252 : : };
253 : :
254 : : void printHelp(std::ostream & out)
255 : 40 : {
256 : 40 : out << "Use one of the following commands:" << std::endl;
257 [ + + ]: 40 : for (const auto & application : clickhouse_applications)
258 : 1280 : out << "clickhouse " << application.first << " [args] " << std::endl;
259 : 40 : }
260 : :
261 : : /// Add an item here to register a new short name
262 : : std::pair<std::string_view, std::string_view> clickhouse_short_names[] =
263 : : {
264 : : {"chl", "local"},
265 : : {"chc", "client"},
266 : : #if USE_CHDIG
267 : : {"chdig", "chdig"},
268 : : #endif
269 : : };
270 : :
271 : : }
272 : :
273 : : static bool isClickhouseApp(std::string_view app_suffix, std::vector<char *> & argv)
274 : 904719 : {
275 [ + + ]: 904719 : for (const auto & [alias, name] : clickhouse_short_names)
276 [ + + ]: 2714157 : if (app_suffix == name
277 [ + - ][ - + ]: 2714157 : && !argv.empty() && (alias == argv[0] || endsWith(argv[0], "/" + std::string(alias))))
[ - + ]
278 : 0 : return true;
279 : :
280 : : /// Use app if the first arg 'app' is passed (the arg should be quietly removed)
281 [ + + ]: 904719 : if (argv.size() >= 2)
282 : 902357 : {
283 : 902357 : auto first_arg = argv.begin() + 1;
284 : :
285 : : /// 'clickhouse --client ...' and 'clickhouse client ...' are Ok
286 [ + + ]: 902357 : if (*first_arg == app_suffix
287 [ + + ][ + + ]: 902357 : || (std::string_view(*first_arg).starts_with("--") && std::string_view(*first_arg).substr(2) == app_suffix))
288 : 92478 : {
289 : 92478 : argv.erase(first_arg);
290 : 92478 : return true;
291 : 92478 : }
292 : 902357 : }
293 : :
294 : : /// Use app if clickhouse binary is run through symbolic link with name clickhouse-app
295 : 812241 : std::string app_name = "clickhouse-" + std::string(app_suffix);
296 [ + - ][ + + ]: 812241 : return !argv.empty() && (app_name == argv[0] || endsWith(argv[0], "/" + app_name));
[ + + ]
297 : 904719 : }
298 : :
299 : : /// Don't allow dlopen in the main ClickHouse binary, because it is harmful and insecure.
300 : : /// We don't use it. But it can be used by some libraries for implementation of "plugins".
301 : : /// We absolutely discourage the ancient technique of loading
302 : : /// 3rd-party uncontrolled dangerous libraries into the process address space,
303 : : /// because it is insane.
304 : : ///
305 : : /// We do allow `dlopen()` in case of OpenSSL FIPS build,
306 : : /// because it requires a FIPS provider (i.e. fips.so), which is loaded dynamically.
307 : : #if !(defined(USE_MUSL) || USE_OPENSSL_FIPS)
308 : : extern "C"
309 : : {
310 : : void * dlopen(const char *, int);
311 : : void * dlmopen(long, const char *, int); // NOLINT
312 : : int dlclose(void *);
313 : : const char * dlerror();
314 : :
315 : : void * dlopen(const char *, int)
316 : 4485 : {
317 : 4485 : return nullptr;
318 : 4485 : }
319 : :
320 : : void * dlmopen(long, const char *, int) // NOLINT
321 : 0 : {
322 : 0 : return nullptr;
323 : 0 : }
324 : :
325 : : int dlclose(void *)
326 : 0 : {
327 : 0 : return 0;
328 : 0 : }
329 : :
330 : : const char * dlerror()
331 : 44 : {
332 : 44 : return "ClickHouse does not allow dynamic library loading";
333 : 44 : }
334 : : }
335 : : #endif
336 : :
337 : : /// Prevent messages from JeMalloc in the release build.
338 : : /// Some of these messages are non-actionable for the users, such as:
339 : : /// <jemalloc>: Number of CPUs detected is not deterministic. Per-CPU arena disabled.
340 : : #if USE_JEMALLOC && defined(NDEBUG) && !defined(SANITIZER)
341 : : extern "C" void (*je_malloc_message)(void *, const char *s);
342 : 207464 : static __attribute__((constructor(0))) void init_je_malloc_message() { je_malloc_message = [](void *, const char *){}; }
343 : : #elif USE_JEMALLOC
344 : : #include <unordered_set>
345 : : /// Ignore messages which can be safely ignored, e.g. EAGAIN on pthread_create
346 : : extern "C" void (*je_malloc_message)(void *, const char * s);
347 : : static __attribute__((constructor(0))) void init_je_malloc_message()
348 : : {
349 : : je_malloc_message = [](void *, const char * str)
350 : : {
351 : : using namespace std::literals;
352 : : static const std::unordered_set<std::string_view> ignore_messages{
353 : : "<jemalloc>: background thread creation failed (11)\n"sv};
354 : :
355 : : std::string_view message_view{str};
356 : : if (ignore_messages.contains(message_view))
357 : : return;
358 : :
359 : : # if defined(SYS_write)
360 : : syscall(SYS_write, 2 /*stderr*/, message_view.data(), message_view.size());
361 : : # else
362 : : write(STDERR_FILENO, message_view.data(), message_view.size());
363 : : # endif
364 : : };
365 : : }
366 : : #endif
367 : :
368 : : /// OpenSSL early initialization.
369 : : /// See also EnvironmentChecks.cpp for other static initializers.
370 : : /// Must be ran after EnvironmentChecks.cpp, as OpenSSL uses SSE4.1 and POPCNT.
371 : : static __attribute__((constructor(202))) void init_ssl()
372 : 207464 : {
373 : 207464 : DB::OpenSSLInitializer::instance();
374 : 207464 : }
375 : :
376 : : /// This allows to implement assert to forbid initialization of a class in static constructors.
377 : : /// Usage:
378 : : ///
379 : : /// extern bool inside_main;
380 : : /// class C { C() { assert(inside_main); } };
381 : : bool inside_main = false;
382 : :
383 : : int main(int argc_, char ** argv_)
384 : 207464 : {
385 : 207464 : inside_main = true;
386 : 207464 : SCOPE_EXIT({ inside_main = false; });
387 : :
388 : : /// PHDR cache is required for query profiler to work reliably
389 : : /// It also speed up exception handling, but exceptions from dynamically loaded libraries (dlopen)
390 : : /// will work only after additional call of this function.
391 : : /// Note: we forbid dlopen in our code.
392 : 207464 : updatePHDRCache();
393 : :
394 : 207464 : #if !defined(USE_MUSL)
395 : 207464 : checkHarmfulEnvironmentVariables(argv_);
396 : 207464 : #endif
397 : :
398 : : /// This is used for testing. For example,
399 : : /// clickhouse-local should be able to run a simple query without throw/catch.
400 [ + + ]: 207464 : if (getenv("CLICKHOUSE_TERMINATE_ON_ANY_EXCEPTION")) // NOLINT(concurrency-mt-unsafe)
401 : 36 : DB::terminate_on_any_exception = true;
402 : :
403 : : /// Reset new handler to default (that throws std::bad_alloc)
404 : : /// It is needed because LLVM library clobbers it.
405 : 207464 : std::set_new_handler(nullptr);
406 : :
407 : 207464 : std::vector<char *> argv(argv_, argv_ + argc_);
408 : :
409 : : /// Print a basic help if nothing was matched
410 : 207464 : MainFunc main_func = printHelpOnError;
411 : :
412 [ + + ]: 207464 : for (auto & application : clickhouse_applications)
413 : 904719 : {
414 [ + + ]: 904719 : if (isClickhouseApp(application.first, argv))
415 : 207335 : {
416 : 207335 : main_func = application.second;
417 : 207335 : break;
418 : 207335 : }
419 : 904719 : }
420 : :
421 : : /// Top-level --help / -h / -? (as the sole argument) should show the dispatcher
422 : : /// help listing all subcommands and exit with code 0. Without this carve-out,
423 : : /// `--help` would match the `startsWith(argv[i], "-h")` rule below and be routed
424 : : /// into clickhouse-client, which treats anything starting with "-h" as a --host
425 : : /// specification and fails.
426 [ + + ][ + + ]: 207464 : if (main_func == printHelpOnError && argv.size() == 2)
427 : 77 : {
428 : 77 : std::string_view arg(argv[1]);
429 [ - + ][ + + ]: 77 : if (arg == "--help" || arg == "-h" || arg == "-?")
[ + + ]
430 : 8 : main_func = mainEntryHelp;
431 : 77 : }
432 : :
433 : : /// If host/port arguments are passed to clickhouse/ch shortcuts,
434 : : /// interpret it as clickhouse-client invocation for usability.
435 [ + + ][ + + ]: 207464 : if (main_func == printHelpOnError && argv.size() >= 2)
436 : 117 : {
437 [ + + ]: 290 : for (size_t i = 1, num_args = argv.size(); i < num_args; ++i)
438 : 181 : {
439 [ + + ][ + + ]: 181 : if ((i + 1 < num_args && argv[i] == std::string_view("--host")) || startsWith(argv[i], "--host=")
[ + + ][ - + ]
440 [ + + ][ - + ]: 181 : || (i + 1 < num_args && argv[i] == std::string_view("--port")) || startsWith(argv[i], "--port=")
[ - + ]
441 [ + + ]: 181 : || startsWith(argv[i], "-h"))
442 : 8 : {
443 : 8 : main_func = mainEntryClickHouseClient;
444 : 8 : break;
445 : 8 : }
446 : 181 : }
447 : 117 : }
448 : :
449 : : /// Interpret binary without argument or with arguments starts with dash
450 : : /// ('-') as clickhouse-local for better usability:
451 : : ///
452 : : /// clickhouse help # dumps help
453 : : /// clickhouse -q 'select 1' # use local
454 : : /// clickhouse # spawn local
455 : : /// clickhouse local # spawn local
456 : : /// clickhouse "select ..." # spawn local
457 : : /// clickhouse /tmp/repro --enable-analyzer
458 : : ///
459 : 207464 : std::error_code ec;
460 [ + + ][ + - ]: 207464 : if (main_func == printHelpOnError && !argv.empty()
461 [ + + ][ + - ]: 207464 : && (argv.size() < 2 || argv[1] != std::string_view("--help"))
462 [ + + ][ + + ]: 207464 : && (argv.size() == 1 || argv[1][0] == '-' || std::string_view(argv[1]).contains(' ')
[ + + ]
463 [ + + ]: 113 : || std::filesystem::is_regular_file(std::filesystem::path{argv[1]}, ec)))
464 : 93 : {
465 : 93 : main_func = mainEntryClickHouseLocal;
466 : 93 : }
467 : :
468 : : /// If the argument looks like a file path but doesn't exist, provide a helpful error
469 : : /// instead of the generic "Use one of the following commands" message.
470 : : /// The check above routes existing files to clickhouse-local, but when the file
471 : : /// doesn't exist, we fall through to `printHelp` which is confusing:
472 : : /// $ clickhouse tests/queries/0_stateless/my_test.sql
473 : : /// Use one of the following commands: ...
474 : : /// We detect file-like arguments by the presence of `/` (path separator)
475 : : /// or `.` (file extension), which distinguishes them from mistyped subcommand
476 : : /// names like "clickhouse sever" where the generic help is appropriate.
477 [ + + ][ + - ]: 207464 : if (main_func == printHelpOnError && argv.size() >= 2)
478 : 20 : {
479 : 20 : std::string_view arg(argv[1]);
480 [ + + ][ + + ]: 20 : if (arg.contains('/') || arg.contains('.'))
481 : 8 : {
482 : 8 : std::cerr << "Error: no such file: " << arg << std::endl;
483 : 8 : std::cerr << "If you intended to run a script, please check the path." << std::endl;
484 : 8 : return 1;
485 : 8 : }
486 : 20 : }
487 : :
488 : 207456 : int exit_code = main_func(static_cast<int>(argv.size()), argv.data());
489 : :
490 : 207456 : return exit_code;
491 : 207464 : }
|