int main(int argc, char** argv) { #ifdef OS_Darwin struct rlimit rlp; if (getrlimit(RLIMIT_NOFILE, &rlp) == 0) { if (rlp.rlim_cur < 1000) { rlp.rlim_cur = 1000; setrlimit(RLIMIT_NOFILE, &rlp); } } #endif { pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_getstacksize(&attr, &defaultStackSize); pthread_attr_destroy(&attr); if (defaultStackSize < 1024 * 1024 * 4) { // 4 megs should be enough for everyone right? defaultStackSize = 1024 * 1024 * 4; } } Rct::findExecutablePath(*argv); struct option opts[] = { { "help", no_argument, 0, 'h' }, { "version", no_argument, 0, 2 }, { "include-path", required_argument, 0, 'I' }, { "isystem", required_argument, 0, 's' }, { "define", required_argument, 0, 'D' }, { "log-file", required_argument, 0, 'L' }, { "setenv", required_argument, 0, 'e' }, { "no-Wall", no_argument, 0, 'W' }, { "Weverything", no_argument, 0, 'u' }, { "cache-AST", required_argument, 0, 'A' }, { "verbose", no_argument, 0, 'v' }, { "job-count", required_argument, 0, 'j' }, { "header-error-job-count", required_argument, 0, 'H' }, { "test", required_argument, 0, 't' }, { "test-timeout", required_argument, 0, 'z' }, { "clean-slate", no_argument, 0, 'C' }, { "disable-sighandler", no_argument, 0, 'x' }, { "silent", no_argument, 0, 'S' }, { "exclude-filter", required_argument, 0, 'X' }, { "socket-file", required_argument, 0, 'n' }, { "config", required_argument, 0, 'c' }, { "no-rc", no_argument, 0, 'N' }, { "data-dir", required_argument, 0, 'd' }, { "ignore-printf-fixits", no_argument, 0, 'F' }, { "no-unlimited-errors", no_argument, 0, 'f' }, { "block-argument", required_argument, 0, 'G' }, { "no-spell-checking", no_argument, 0, 'l' }, { "large-by-value-copy", required_argument, 0, 'r' }, { "disallow-multiple-sources", no_argument, 0, 'm' }, { "no-startup-project", no_argument, 0, 'o' }, { "no-no-unknown-warnings-option", no_argument, 0, 'Y' }, { "ignore-compiler", required_argument, 0, 'b' }, { "watch-system-paths", no_argument, 0, 'w' }, { "rp-visit-file-timeout", required_argument, 0, 'Z' }, { "rp-indexer-message-timeout", required_argument, 0, 'T' }, { "rp-connect-timeout", required_argument, 0, 'O' }, { "rp-connect-attempts", required_argument, 0, 3 }, { "rp-nice-value", required_argument, 0, 'a' }, { "thread-stack-size", required_argument, 0, 'k' }, { "suspend-rp-on-crash", no_argument, 0, 'q' }, { "rp-log-to-syslog", no_argument, 0, 7 }, { "start-suspended", no_argument, 0, 'Q' }, { "separate-debug-and-release", no_argument, 0, 'E' }, { "max-crash-count", required_argument, 0, 'K' }, { "completion-cache-size", required_argument, 0, 'i' }, { "completion-no-filter", no_argument, 0, 8 }, { "extra-compilers", required_argument, 0, 'U' }, { "allow-Wpedantic", no_argument, 0, 'P' }, { "enable-compiler-manager", no_argument, 0, 'R' }, { "enable-NDEBUG", no_argument, 0, 'g' }, { "progress", no_argument, 0, 'p' }, { "max-file-map-cache-size", required_argument, 0, 'y' }, #ifdef OS_FreeBSD { "filemanager-watch", no_argument, 0, 'M' }, #else { "no-filemanager-watch", no_argument, 0, 'M' }, #endif { "no-file-lock", no_argument, 0, 13 }, { "pch-enabled", no_argument, 0, 14 }, { "no-filesystem-watcher", no_argument, 0, 'B' }, { "arg-transform", required_argument, 0, 'V' }, { "no-comments", no_argument, 0, 1 }, #ifdef OS_Darwin { "launchd", no_argument, 0, 4 }, #endif { "inactivity-timeout", required_argument, 0, 5 }, { "daemon", no_argument, 0, 6 }, { "log-file-log-level", required_argument, 0, 9 }, { "watch-sources-only", no_argument, 0, 10 }, { "debug-locations", no_argument, 0, 11 }, { "tcp-port", required_argument, 0, 12 }, { 0, 0, 0, 0 } }; const String shortOptions = Rct::shortOptions(opts); if (getenv("RTAGS_DUMP_UNUSED")) { String unused; for (int i=0; i<26; ++i) { if (!shortOptions.contains('a' + i)) unused.append('a' + i); if (!shortOptions.contains('A' + i)) unused.append('A' + i); } printf("Unused: %s\n", unused.constData()); for (int i=0; opts[i].name; ++i) { if (opts[i].name) { if (!opts[i].val) { printf("No shortoption for %s\n", opts[i].name); } else if (opts[i].name[0] != opts[i].val) { printf("Not ideal option for %s|%c\n", opts[i].name, opts[i].val); } } } return 0; } bool daemon = false; List<String> argCopy; List<char*> argList; { bool norc = false; Path rcfile = Path::home() + ".rdmrc"; opterr = 0; StackBuffer<128, char*> originalArgv(argc); memcpy(originalArgv, argv, sizeof(char*) * argc); /* getopt will molest argv by moving pointers around when it sees * fit. Their idea of an optional argument is different from ours so we * have to take a copy of argv before they get their sticky fingers all * over it. * * We think this should be okay for an optional argument: * -s something * * They only populate optarg if you do: * -ssomething. * * We don't want to copy argv into argList before processing rc files * since command line args should take precedence over things in rc * files. * */ while (true) { const int c = getopt_long(argc, argv, shortOptions.constData(), opts, 0); if (c == -1) break; switch (c) { case 'N': norc = true; break; case 'c': rcfile = optarg; break; default: break; } } opterr = 1; argList.append(argv[0]); if (!norc) { String rc = Path("/etc/rdmrc").readAll(); if (!rc.isEmpty()) { for (const String& s : rc.split('\n')) { if (!s.isEmpty() && !s.startsWith('#')) argCopy += s.split(' '); } } if (!rcfile.isEmpty()) { rc = rcfile.readAll(); if (!rc.isEmpty()) { for (const String& s : rc.split('\n')) { if (!s.isEmpty() && !s.startsWith('#')) argCopy += s.split(' '); } } } const int s = argCopy.size(); for (int i=0; i<s; ++i) { String &arg = argCopy.at(i); if (!arg.isEmpty()) argList.append(arg.data()); } } for (int i=1; i<argc; ++i) argList.append(originalArgv[i]); optind = 1; } Server::Options serverOpts; serverOpts.threadStackSize = defaultStackSize; serverOpts.socketFile = String::format<128>("%s.rdm", Path::home().constData()); serverOpts.jobCount = std::max(2, ThreadPool::idealThreadCount()); serverOpts.headerErrorJobCount = -1; serverOpts.rpVisitFileTimeout = DEFAULT_RP_VISITFILE_TIMEOUT; serverOpts.rpIndexDataMessageTimeout = DEFAULT_RP_INDEXER_MESSAGE_TIMEOUT; serverOpts.rpConnectTimeout = DEFAULT_RP_CONNECT_TIMEOUT; serverOpts.rpConnectAttempts = DEFAULT_RP_CONNECT_ATTEMPTS; serverOpts.maxFileMapScopeCacheSize = DEFAULT_RDM_MAX_FILE_MAP_CACHE_SIZE; serverOpts.rpNiceValue = INT_MIN; serverOpts.options = Server::Wall|Server::SpellChecking; serverOpts.maxCrashCount = DEFAULT_MAX_CRASH_COUNT; serverOpts.completionCacheSize = DEFAULT_COMPLETION_CACHE_SIZE; #ifdef OS_FreeBSD serverOpts.options |= Server::NoFileManagerWatch; #endif // #ifndef NDEBUG // serverOpts.options |= Server::SuspendRPOnCrash; // #endif serverOpts.excludeFilters = String(EXCLUDEFILTER_DEFAULT).split(';'); serverOpts.dataDir = String::format<128>("%s.rtags", Path::home().constData()); const char *logFile = 0; Flags<LogFileFlag> logFlags = DontRotate; LogLevel logLevel(LogLevel::Error); LogLevel logFileLogLevel(LogLevel::Error); bool sigHandler = true; assert(Path::home().endsWith('/')); int argCount = argList.size(); char **args = argList.data(); bool defaultDataDir = true; int inactivityTimeout = 0; while (true) { const int c = getopt_long(argCount, args, shortOptions.constData(), opts, 0); if (c == -1) break; switch (c) { case 'N': case 'c': // ignored break; case 'S': logLevel = LogLevel::None; break; case 'X': serverOpts.excludeFilters += String(optarg).split(';'); break; case 'G': serverOpts.blockedArguments << optarg; break; case 1: serverOpts.options |= Server::NoComments; break; case 10: serverOpts.options |= Server::WatchSourcesOnly; break; case 11: if (!strcmp(optarg, "clear") || !strcmp(optarg, "none")) { serverOpts.debugLocations.clear(); } else { serverOpts.debugLocations << optarg; } break; case 12: serverOpts.tcpPort = atoi(optarg); if (!serverOpts.tcpPort) { fprintf(stderr, "Invalid port %s for --tcp-port\n", optarg); return 1; } break; case 13: serverOpts.options |= Server::NoFileLock; break; case 14: serverOpts.options |= Server::PCHEnabled; break; case 2: fprintf(stdout, "%s\n", RTags::versionString().constData()); return 0; case 6: daemon = true; logLevel = LogLevel::None; break; case 9: if (!strcasecmp(optarg, "verbose-debug")) { logFileLogLevel = LogLevel::VerboseDebug; } else if (!strcasecmp(optarg, "debug")) { logFileLogLevel = LogLevel::Debug; } else if (!strcasecmp(optarg, "warning")) { logFileLogLevel = LogLevel::Warning; } else if (!strcasecmp(optarg, "error")) { logFileLogLevel = LogLevel::Error; } else { fprintf(stderr, "Unknown log level: %s options are error, warning, debug or verbose-debug\n", optarg); return 1; } break; case 'U': serverOpts.extraCompilers.append(std::regex(optarg)); break; case 'E': serverOpts.options |= Server::SeparateDebugAndRelease; break; case 'g': serverOpts.options |= Server::EnableNDEBUG; break; case 'Q': serverOpts.options |= Server::StartSuspended; break; case 'Z': serverOpts.rpVisitFileTimeout = atoi(optarg); if (serverOpts.rpVisitFileTimeout < 0) { fprintf(stderr, "Invalid argument to -Z %s\n", optarg); return 1; } if (!serverOpts.rpVisitFileTimeout) serverOpts.rpVisitFileTimeout = -1; break; case 'y': serverOpts.maxFileMapScopeCacheSize = atoi(optarg); if (serverOpts.maxFileMapScopeCacheSize <= 0) { fprintf(stderr, "Invalid argument to -y %s\n", optarg); return 1; } break; case 'O': serverOpts.rpConnectTimeout = atoi(optarg); if (serverOpts.rpConnectTimeout < 0) { fprintf(stderr, "Invalid argument to -O %s\n", optarg); return 1; } break; case 3: serverOpts.rpConnectAttempts = atoi(optarg); if (serverOpts.rpConnectAttempts <= 0) { fprintf(stderr, "Invalid argument to --rp-connect-attempts %s\n", optarg); return 1; } break; case 'k': serverOpts.threadStackSize = atoi(optarg); if (serverOpts.threadStackSize < 0) { fprintf(stderr, "Invalid argument to -k %s\n", optarg); return 1; } break; case 'b': serverOpts.ignoredCompilers.insert(Path::resolved(optarg)); break; case 't': { Path test(optarg); if (!test.resolve() || !test.isFile()) { fprintf(stderr, "%s doesn't seem to be a file\n", optarg); return 1; } serverOpts.tests += test; break; } case 'z': serverOpts.testTimeout = atoi(optarg); if (serverOpts.testTimeout <= 0) { fprintf(stderr, "Invalid argument to -z %s\n", optarg); return 1; } break; case 'n': serverOpts.socketFile = optarg; break; case 'd': defaultDataDir = false; serverOpts.dataDir = String::format<128>("%s", Path::resolved(optarg).constData()); break; case 'h': usage(stdout); return 0; case 'Y': serverOpts.options |= Server::NoNoUnknownWarningsOption; break; case 'p': serverOpts.options |= Server::Progress; break; case 'R': serverOpts.options |= Server::EnableCompilerManager; break; case 'm': serverOpts.options |= Server::DisallowMultipleSources; break; case 'o': serverOpts.options |= Server::NoStartupCurrentProject; break; case 'w': serverOpts.options |= Server::WatchSystemPaths; break; case 'q': serverOpts.options |= Server::SuspendRPOnCrash; break; case 'M': #ifdef OS_FreeBSD serverOpts.options &= ~Server::NoFileManagerWatch; #else serverOpts.options |= Server::NoFileManagerWatch; #endif break; case 'B': serverOpts.options |= Server::NoFileSystemWatch; break; case 'V': serverOpts.argTransform = Process::findCommand(optarg); if (strlen(optarg) && serverOpts.argTransform.isEmpty()) { fprintf(stderr, "Invalid argument to -V. Can't resolve %s", optarg); return 1; } break; case 'F': serverOpts.options |= Server::IgnorePrintfFixits; break; case 'f': serverOpts.options |= Server::NoUnlimitedErrors; break; case 'l': serverOpts.options &= ~Server::SpellChecking; break; case 'W': serverOpts.options &= ~Server::Wall; break; case 'u': serverOpts.options |= Server::Weverything; break; case 'P': serverOpts.options |= Server::AllowPedantic; break; case 'C': serverOpts.options |= Server::ClearProjects; break; case 'e': putenv(optarg); break; case 'x': sigHandler = false; break; case 'K': serverOpts.maxCrashCount = atoi(optarg); if (serverOpts.maxCrashCount <= 0) { fprintf(stderr, "Invalid argument to -K %s\n", optarg); return 1; } break; case 'i': serverOpts.completionCacheSize = atoi(optarg); if (serverOpts.completionCacheSize <= 0) { fprintf(stderr, "Invalid argument to -i %s\n", optarg); return 1; } break; case 'T': serverOpts.rpIndexDataMessageTimeout = atoi(optarg); if (serverOpts.rpIndexDataMessageTimeout <= 0) { fprintf(stderr, "Can't parse argument to -T %s.\n", optarg); return 1; } break; case 'a': { bool ok; serverOpts.rpNiceValue = String(optarg).toLong(&ok); if (!ok) { fprintf(stderr, "Can't parse argument to -a %s.\n", optarg); return 1; } break; } case 'j': serverOpts.jobCount = atoi(optarg); if (serverOpts.jobCount < 0) { fprintf(stderr, "Can't parse argument to -j %s. -j must be a positive integer.\n", optarg); return 1; } break; case 'H': serverOpts.headerErrorJobCount = atoi(optarg); if (serverOpts.headerErrorJobCount < 0) { fprintf(stderr, "Can't parse argument to -H %s. -J must be a positive integer.\n", optarg); return 1; } break; case 'r': { int large = atoi(optarg); if (large <= 0) { fprintf(stderr, "Can't parse argument to -r %s\n", optarg); return 1; } serverOpts.defaultArguments.append("-Wlarge-by-value-copy=" + String(optarg)); // ### not quite working break; } case 'D': { const char *eq = strchr(optarg, '='); Source::Define def; if (!eq) { def.define = optarg; } else { def.define = String(optarg, eq - optarg); def.value = eq + 1; } serverOpts.defines.append(def); break; } case 'I': serverOpts.includePaths.append(Source::Include(Source::Include::Type_Include, Path::resolved(optarg))); break; case 's': serverOpts.includePaths.append(Source::Include(Source::Include::Type_System, Path::resolved(optarg))); break; case 'L': logFile = optarg; logLevel = LogLevel::None; break; case 'v': if (logLevel != LogLevel::None) ++logLevel; break; #ifdef OS_Darwin case 4: serverOpts.options |= Server::Launchd; break; #endif case 5: inactivityTimeout = atoi(optarg); // seconds. if (inactivityTimeout <= 0) { fprintf(stderr, "Invalid argument to --inactivity-timeout %s\n", optarg); return 1; } break; case 7: serverOpts.options |= Server::RPLogToSyslog; break; case 8: serverOpts.options |= Server::CompletionsNoFilter; break; case '?': { fprintf(stderr, "Run rdm --help for help\n"); return 1; } } } if (optind < argCount) { fprintf(stderr, "rdm: unexpected option -- '%s'\n", args[optind]); return 1; } if (daemon) { switch (fork()) { case -1: fprintf(stderr, "Failed to fork (%d) %s\n", errno, strerror(errno)); return 1; case 0: setsid(); switch (fork()) { case -1: fprintf(stderr, "Failed to fork (%d) %s\n", errno, strerror(errno)); return 1; case 0: break; default: return 0; } break; default: return 0; } } if (serverOpts.headerErrorJobCount == -1) { serverOpts.headerErrorJobCount = std::max(1, serverOpts.jobCount / 2); } else { serverOpts.headerErrorJobCount = std::min(serverOpts.headerErrorJobCount, serverOpts.jobCount); } if (sigHandler) { signal(SIGSEGV, sigSegvHandler); signal(SIGILL, sigSegvHandler); signal(SIGABRT, sigSegvHandler); } // Shell-expand logFile Path logPath(logFile); logPath.resolve(); if (!initLogging(argv[0], LogStderr, logLevel, logPath.constData(), logFlags, logFileLogLevel)) { fprintf(stderr, "Can't initialize logging with %d %s %s\n", logLevel.toInt(), logFile ? logFile : "", logFlags.toString().constData()); return 1; } #ifdef OS_Darwin if (serverOpts.options & Server::Launchd) { // Clamp inactivity timeout. launchd starts to worry if the // process runs for less than 10 seconds. static const int MIN_INACTIVITY_TIMEOUT = 15; // includes // fudge factor. if (inactivityTimeout < MIN_INACTIVITY_TIMEOUT) { inactivityTimeout = MIN_INACTIVITY_TIMEOUT; fprintf(stderr, "launchd mode - clamped inactivity timeout to %d to avoid launchd warnings.\n", inactivityTimeout); } } #endif EventLoop::SharedPtr loop(new EventLoop); loop->init(EventLoop::MainEventLoop|EventLoop::EnableSigIntHandler|EventLoop::EnableSigTermHandler); std::shared_ptr<Server> server(new Server); if (!serverOpts.tests.isEmpty()) { char buf[1024]; Path path; while (true) { strcpy(buf, "/tmp/rtags-test-XXXXXX"); if (!mkdtemp(buf)) { fprintf(stderr, "Failed to mkdtemp (%d)\n", errno); return 1; } path = buf; path.resolve(); break; } serverOpts.dataDir = path; strcpy(buf, "/tmp/rtags-sock-XXXXXX"); const int fd = mkstemp(buf); if (fd == -1) { fprintf(stderr, "Failed to mkstemp (%d)\n", errno); return 1; } close(fd); serverOpts.socketFile = buf; serverOpts.socketFile.resolve(); } if (defaultDataDir) { Path migration = String::format<128>("%s.rtags-file", Path::home().constData()); if (migration.isDir()) { Rct::removeDirectory(serverOpts.dataDir); rename(migration.constData(), serverOpts.dataDir.constData()); error() << "Migrated datadir from ~/.rtags-file ~/.rtags"; } } serverOpts.dataDir = serverOpts.dataDir.ensureTrailingSlash(); if (!server->init(serverOpts)) { cleanupLogging(); return 1; } if (!serverOpts.tests.isEmpty()) { return server->runTests() ? 0 : 1; } loop->setInactivityTimeout(inactivityTimeout * 1000); loop->exec(); const int ret = server->exitCode(); server.reset(); cleanupLogging(); return ret; }
int main(int argc, char** argv) { RemoveCrashDump removeCrashDump; #ifdef OS_Darwin struct rlimit rlp; if (getrlimit(RLIMIT_NOFILE, &rlp) == 0) { if (rlp.rlim_cur < 1000) { rlp.rlim_cur = 1000; setrlimit(RLIMIT_NOFILE, &rlp); } } #endif Rct::findExecutablePath(*argv); bool daemon = false; Server::Options serverOpts; const char * runtimeDir = getenv("XDG_RUNTIME_DIR"); if (runtimeDir == NULL) { serverOpts.socketFile = String::format<128>("%s.rdm", Path::home().constData()); } else { serverOpts.socketFile = String::format<1024>("%s/rdm.socket", runtimeDir); } serverOpts.jobCount = std::max(2, ThreadPool::idealThreadCount()); serverOpts.headerErrorJobCount = -1; serverOpts.rpVisitFileTimeout = DEFAULT_RP_VISITFILE_TIMEOUT; serverOpts.rpIndexDataMessageTimeout = DEFAULT_RP_INDEXER_MESSAGE_TIMEOUT; serverOpts.rpConnectTimeout = DEFAULT_RP_CONNECT_TIMEOUT; serverOpts.rpConnectAttempts = DEFAULT_RP_CONNECT_ATTEMPTS; serverOpts.maxFileMapScopeCacheSize = DEFAULT_RDM_MAX_FILE_MAP_CACHE_SIZE; serverOpts.errorLimit = DEFAULT_ERROR_LIMIT; serverOpts.rpNiceValue = INT_MIN; serverOpts.options = Server::Wall|Server::SpellChecking; serverOpts.maxCrashCount = DEFAULT_MAX_CRASH_COUNT; serverOpts.completionCacheSize = DEFAULT_COMPLETION_CACHE_SIZE; serverOpts.maxIncludeCompletionDepth = DEFAULT_MAX_INCLUDE_COMPLETION_DEPTH; serverOpts.rp = defaultRP(); strcpy(crashDumpFilePath, "crash.dump"); #ifdef FILEMANAGER_OPT_IN serverOpts.options |= Server::NoFileManagerWatch; #endif // #ifndef NDEBUG // serverOpts.options |= Server::SuspendRPOnCrash; // #endif serverOpts.dataDir = String::format<128>("%s.rtags", Path::home().constData()); if (!serverOpts.dataDir.exists()) { const char * dataDir = getenv("XDG_CACHE_HOME"); serverOpts.dataDir = dataDir ? dataDir : Path::home() + ".cache"; serverOpts.dataDir += "/rtags/"; serverOpts.dataDir.mkdir(Path::Recursive); } Path logFile; Flags<LogFlag> logFlags = DontRotate|LogStderr; LogLevel logLevel(LogLevel::Error); LogLevel logFileLogLevel(LogLevel::Error); bool sigHandler = true; assert(Path::home().endsWith('/')); int inactivityTimeout = 0; const std::initializer_list<CommandLineParser::Option<OptionType> > opts = { { None, 0, 0, CommandLineParser::NoValue, "Options:" }, { Help, "help", 'h', CommandLineParser::NoValue, "Display this page." }, { Version, "version", 0, CommandLineParser::NoValue, "Display version." }, { IncludePath, "include-path", 'I', CommandLineParser::Required, "Add additional include path to clang." }, { Isystem, "isystem", 's', CommandLineParser::Required, "Add additional system include path to clang." }, { Define, "define", 'D', CommandLineParser::Required, "Add additional define directive to clang." }, { DefaultArgument, "default-argument", 0, CommandLineParser::Required, "Add additional argument to clang." }, { LogFile, "log-file", 'L', CommandLineParser::Required, "Log to this file." }, { CrashDumpFile, "crash-dump-file", 0, CommandLineParser::Required, "File to dump crash log to (default is <datadir>/crash.dump)." }, { SetEnv, "setenv", 'e', CommandLineParser::Required, "Set this environment variable (--setenv \"foobar=1\")." }, { NoWall, "no-Wall", 'W', CommandLineParser::NoValue, "Don't use -Wall." }, { Weverything, "Weverything", 'u', CommandLineParser::NoValue, "Use -Weverything." }, { Verbose, "verbose", 'v', CommandLineParser::NoValue, "Change verbosity, multiple -v's are allowed." }, { JobCount, "job-count", 'j', CommandLineParser::Required, String::format("Spawn this many concurrent processes for indexing (default %d).", std::max(2, ThreadPool::idealThreadCount())) }, { HeaderErrorJobCount, "header-error-job-count", 'H', CommandLineParser::Required, "Allow this many concurrent header error jobs (default std::max(1, --job-count / 2))." }, { Test, "test", 't', CommandLineParser::Required, "Run this test." }, { TestTimeout, "test-timeout", 'z', CommandLineParser::Required, "Timeout for test to complete." }, { CleanSlate, "clean-slate", 'C', CommandLineParser::NoValue, "Clear out all data." }, { DisableSigHandler, "disable-sighandler", 'x', CommandLineParser::NoValue, "Disable signal handler to dump stack for crashes." }, { Silent, "silent", 'S', CommandLineParser::NoValue, "No logging to stdout/stderr." }, { ExcludeFilter, "exclude-filter", 'X', CommandLineParser::Required, "Files to exclude from rdm, default \"" DEFAULT_EXCLUDEFILTER "\"." }, { SocketFile, "socket-file", 'n', CommandLineParser::Required, "Use this file for the server socket (default ~/.rdm)." }, { DataDir, "data-dir", 'd', CommandLineParser::Required, "Use this directory to store persistent data (default $XDG_CACHE_HOME/rtags otherwise ~/.cache/rtags)." }, { IgnorePrintfFixits, "ignore-printf-fixits", 'F', CommandLineParser::NoValue, "Disregard any clang fixit that looks like it's trying to fix format for printf and friends." }, { ErrorLimit, "error-limit", 'f', CommandLineParser::Required, "Set error limit to argument (-ferror-limit={arg} (default " STR(DEFAULT_ERROR_LIMIT) ")." }, { BlockArgument, "block-argument", 'G', CommandLineParser::Required, "Block this argument from being passed to clang. E.g. rdm --block-argument -fno-inline" }, { NoSpellChecking, "no-spell-checking", 'l', CommandLineParser::NoValue, "Don't pass -fspell-checking." }, { LargeByValueCopy, "large-by-value-copy", 'r', CommandLineParser::Required, "Use -Wlarge-by-value-copy=[arg] when invoking clang." }, { AllowMultipleSources, "allow-multiple-sources", 'm', CommandLineParser::NoValue, "Don't merge source files added with -c." }, { NoStartupProject, "no-startup-project", 'o', CommandLineParser::NoValue, "Don't restore the last current project on startup." }, { NoNoUnknownWarningsOption, "no-no-unknown-warnings-option", 'Y', CommandLineParser::NoValue, "Don't pass -Wno-unknown-warning-option." }, { IgnoreCompiler, "ignore-compiler", 'b', CommandLineParser::Required, "Ignore this compiler." }, { CompilerWrappers, "compiler-wrappers", 0, CommandLineParser::Required, "Consider these filenames compiler wrappers (split on ;), default " DEFAULT_COMPILER_WRAPPERS "\"." }, { WatchSystemPaths, "watch-system-paths", 'w', CommandLineParser::NoValue, "Watch system paths for changes." }, { RpVisitFileTimeout, "rp-visit-file-timeout", 'Z', CommandLineParser::Required, "Timeout for rp visitfile commands in ms (0 means no timeout) (default " STR(DEFAULT_RP_VISITFILE_TIMEOUT) ")." }, { RpIndexerMessageTimeout, "rp-indexer-message-timeout", 'T', CommandLineParser::Required, "Timeout for rp indexer-message in ms (0 means no timeout) (default " STR(DEFAULT_RP_INDEXER_MESSAGE_TIMEOUT) ")." }, { RpConnectTimeout, "rp-connect-timeout", 'O', CommandLineParser::Required, "Timeout for connection from rp to rdm in ms (0 means no timeout) (default " STR(DEFAULT_RP_CONNECT_TIMEOUT) ")." }, { RpConnectAttempts, "rp-connect-attempts", 0, CommandLineParser::Required, "Number of times rp attempts to connect to rdm before giving up. (default " STR(DEFAULT_RP_CONNECT_ATTEMPTS) ")." }, { RpNiceValue, "rp-nice-value", 'a', CommandLineParser::Required, "Nice value to use for rp (nice(2)) (default is no nicing)." }, { SuspendRpOnCrash, "suspend-rp-on-crash", 'q', CommandLineParser::NoValue, "Suspend rp in SIGSEGV handler (default " DEFAULT_SUSPEND_RP ")." }, { RpLogToSyslog, "rp-log-to-syslog", 0, CommandLineParser::NoValue, "Make rp log to syslog." }, { StartSuspended, "start-suspended", 'Q', CommandLineParser::NoValue, "Start out suspended (no reindexing enabled)." }, { SeparateDebugAndRelease, "separate-debug-and-release", 'E', CommandLineParser::NoValue, "Normally rdm doesn't consider release and debug as different builds. Pass this if you want it to." }, { Separate32BitAnd64Bit, "separate-32-bit-and-64-bit", 0, CommandLineParser::NoValue, "Normally rdm doesn't consider -m32 and -m64 as different builds. Pass this if you want it to." }, { SourceIgnoreIncludePathDifferencesInUsr, "ignore-include-path-differences-in-usr", 0, CommandLineParser::NoValue, "Don't consider sources that only differ in includepaths within /usr (not including /usr/home/) as different builds." }, { MaxCrashCount, "max-crash-count", 'K', CommandLineParser::Required, "Max number of crashes before giving up a sourcefile (default " STR(DEFAULT_MAX_CRASH_COUNT) ")." }, { CompletionCacheSize, "completion-cache-size", 'i', CommandLineParser::Required, "Number of translation units to cache (default " STR(DEFAULT_COMPLETION_CACHE_SIZE) ")." }, { CompletionNoFilter, "completion-no-filter", 0, CommandLineParser::NoValue, "Don't filter private members and destructors from completions." }, { CompletionLogs, "completion-logs", 0, CommandLineParser::NoValue, "Log more info about completions." }, { MaxIncludeCompletionDepth, "max-include-completion-depth", 0, CommandLineParser::Required, "Max recursion depth for header completion (default " STR(DEFAULT_MAX_INCLUDE_COMPLETION_DEPTH) ")." }, { AllowWpedantic, "allow-Wpedantic", 'P', CommandLineParser::NoValue, "Don't strip out -Wpedantic. This can cause problems in certain projects." }, { AllowWErrorAndWFatalErrors, "allow-Werror", 0, CommandLineParser::NoValue, "Don't strip out -Werror and -Wfatal-errors. By default these are stripped out. " }, { EnableCompilerManager, "enable-compiler-manager", 'R', CommandLineParser::NoValue, "Query compilers for their actual include paths instead of letting clang use its own." }, { EnableNDEBUG, "enable-NDEBUG", 'g', CommandLineParser::NoValue, "Don't remove -DNDEBUG from compile lines." }, { Progress, "progress", 'p', CommandLineParser::NoValue, "Report compilation progress in diagnostics output." }, { MaxFileMapCacheSize, "max-file-map-cache-size", 'y', CommandLineParser::Required, "Max files to cache per query (Should not exceed maximum number of open file descriptors allowed per process) (default " STR(DEFAULT_RDM_MAX_FILE_MAP_CACHE_SIZE) ")." }, #ifdef FILEMANAGER_OPT_IN { FileManagerWatch, "filemanager-watch", 'M', CommandLineParser::NoValue, "Use a file system watcher for filemanager." }, #else { NoFileManagerWatch, "no-filemanager-watch", 'M', CommandLineParser::NoValue, "Don't use a file system watcher for filemanager." }, #endif { NoFileManager, "no-filemanager", 0, CommandLineParser::NoValue, "Don't scan project directory for files. (rc -P won't work)." }, { NoFileLock, "no-file-lock", 0, CommandLineParser::NoValue, "Disable file locking. Not entirely safe but might improve performance on certain systems." }, { PchEnabled, "pch-enabled", 0, CommandLineParser::NoValue, "Enable PCH (experimental)." }, { NoFilesystemWatcher, "no-filesystem-watcher", 'B', CommandLineParser::NoValue, "Disable file system watching altogether. Reindexing has to be triggered manually." }, { ArgTransform, "arg-transform", 'V', CommandLineParser::Required, "Use arg to transform arguments. [arg] should be executable with (execv(3))." }, { NoComments, "no-comments", 0, CommandLineParser::NoValue, "Don't parse/store doxygen comments." }, #ifdef RTAGS_HAS_LAUNCHD { Launchd, "launchd", 0, CommandLineParser::NoValue, "Run as a launchd job (use launchd API to retrieve socket opened by launchd on rdm's behalf)." }, #endif { InactivityTimeout, "inactivity-timeout", 0, CommandLineParser::Required, "Time in seconds after which rdm will quit if there's been no activity (N.B., once rdm has quit, something will need to re-run it!)." }, { Daemon, "daemon", 0, CommandLineParser::NoValue, "Run as daemon (detach from terminal)." }, { LogFileLogLevel, "log-file-log-level", 0, CommandLineParser::Required, "Log level for log file (default is error), options are: error, warning, debug or verbose-debug." }, { WatchSourcesOnly, "watch-sources-only", 0, CommandLineParser::NoValue, "Only watch source files (not dependencies)." }, { DebugLocations, "debug-locations", 0, CommandLineParser::NoValue, "Set debug locations." }, { ValidateFileMaps, "validate-file-maps", 0, CommandLineParser::NoValue, "Spend some time validating project data on startup." }, { TcpPort, "tcp-port", 0, CommandLineParser::Required, "Listen on this tcp socket (default none)." }, { RpPath, "rp-path", 0, CommandLineParser::Required, String::format<256>("Path to rp (default %s).", defaultRP().constData()) }, { LogTimestamp, "log-timestamp", 0, CommandLineParser::NoValue, "Add timestamp to logs." }, { LogFlushOption, "log-flush", 0, CommandLineParser::NoValue, "Flush stderr/stdout after each log." }, { SandboxRoot, "sandbox-root", 0, CommandLineParser::Required, "Create index using relative paths by stripping dir (enables copying of tag index db files without need to reindex)." }, { PollTimer, "poll-timer", 0, CommandLineParser::Required, "Poll the database of the current project every <arg> seconds. " }, { NoRealPath, "no-realpath", 0, CommandLineParser::NoValue, "Don't use realpath(3) for files" }, { Noop, "config", 'c', CommandLineParser::Required, "Use this file (instead of ~/.rdmrc)." }, { Noop, "no-rc", 'N', CommandLineParser::NoValue, "Don't load any rc files." } }; std::function<CommandLineParser::ParseStatus(OptionType type, String &&value, size_t &idx, const List<String> &args)> cb; cb = [&](OptionType type, String &&value, size_t &, const List<String> &) -> CommandLineParser::ParseStatus { switch (type) { case None: case Noop: break; case Help: { CommandLineParser::help(stdout, Rct::executablePath().fileName(), opts); return { String(), CommandLineParser::Parse_Ok }; } case Version: { fprintf(stdout, "%s\n", RTags::versionString().constData()); return { String(), CommandLineParser::Parse_Ok }; } case IncludePath: { serverOpts.includePaths.append(Source::Include(Source::Include::Type_Include, Path::resolved(value))); break; } case Isystem: { serverOpts.includePaths.append(Source::Include(Source::Include::Type_System, Path::resolved(value))); break; } case Define: { const size_t eq = value.indexOf('='); Source::Define def; if (eq == String::npos) { def.define = std::move(value); } else { def.define = value.left(eq); def.value = value.mid(eq + 1); } serverOpts.defines.append(def); break; } case DefaultArgument: { serverOpts.defaultArguments.append(std::move(value)); break; } case LogFile: { logFile = std::move(value); logFile.resolve(); logLevel = LogLevel::None; break; } case CrashDumpFile: { strncpy(crashDumpFilePath, value.constData(), sizeof(crashDumpFilePath) - 1); break; } case SetEnv: { putenv(&value[0]); break; } case NoWall: { serverOpts.options &= ~Server::Wall; break; } case Weverything: { serverOpts.options |= Server::Weverything; break; } case Verbose: { if (logLevel != LogLevel::None) ++logLevel; break; } case JobCount: { bool ok; serverOpts.jobCount = String(value).toULong(&ok); if (!ok) { return { String::format<1024>("Can't parse argument to -j %s. -j must be a positive integer.\n", value.constData()), CommandLineParser::Parse_Error }; } break; } case HeaderErrorJobCount: { bool ok; serverOpts.headerErrorJobCount = String(value).toULong(&ok); if (!ok) { return { String::format<1024>("Can't parse argument to -H %s. -H must be a positive integer.", value.constData()), CommandLineParser::Parse_Error }; } break; } case Test: { Path test(value); if (!test.resolve() || !test.isFile()) { return { String::format<1024>("%s doesn't seem to be a file", value.constData()), CommandLineParser::Parse_Error }; } serverOpts.tests += test; break; } case TestTimeout: { serverOpts.testTimeout = atoi(value.constData()); if (serverOpts.testTimeout <= 0) { return { String::format<1024>("Invalid argument to -z %s", value.constData()), CommandLineParser::Parse_Error }; } break; } case PollTimer: { serverOpts.pollTimer = atoi(value.constData()); if (serverOpts.pollTimer < 0) { return { String::format<1024>("Invalid argument to --poll-timer %s", value.constData()), CommandLineParser::Parse_Error }; } break; } case CleanSlate: { serverOpts.options |= Server::ClearProjects; break; } case DisableSigHandler: { sigHandler = false; break; } case Silent: { logLevel = LogLevel::None; break; } case ExcludeFilter: { serverOpts.excludeFilters += String(value).split(';'); break; } case SocketFile: { serverOpts.socketFile = std::move(value); serverOpts.socketFile.resolve(); break; } case DataDir: { serverOpts.dataDir = String::format<128>("%s", Path::resolved(value).constData()); break; } case IgnorePrintfFixits: { serverOpts.options |= Server::IgnorePrintfFixits; break; } case ErrorLimit: { bool ok; serverOpts.errorLimit = String(value).toULong(&ok); if (!ok) { return { String::format<1024>("Can't parse argument to --error-limit %s", value.constData()), CommandLineParser::Parse_Error }; } break; } case BlockArgument: { serverOpts.blockedArguments << value; break; } case NoSpellChecking: { serverOpts.options &= ~Server::SpellChecking; break; } case LargeByValueCopy: { int large = atoi(value.constData()); if (large <= 0) { return { String::format<1024>("Can't parse argument to -r %s", value.constData()), CommandLineParser::Parse_Error }; } serverOpts.defaultArguments.append("-Wlarge-by-value-copy=" + String(value)); // ### not quite working break; } case AllowMultipleSources: { serverOpts.options |= Server::AllowMultipleSources; break; } case NoStartupProject: { serverOpts.options |= Server::NoStartupCurrentProject; break; } case NoNoUnknownWarningsOption: { serverOpts.options |= Server::NoNoUnknownWarningsOption; break; } case IgnoreCompiler: { serverOpts.ignoredCompilers.insert(Path::resolved(value)); break; } case CompilerWrappers: { serverOpts.compilerWrappers = String(value).split(";", String::SkipEmpty).toSet(); break; } case WatchSystemPaths: { serverOpts.options |= Server::WatchSystemPaths; break; } case RpVisitFileTimeout: { serverOpts.rpVisitFileTimeout = atoi(value.constData()); if (serverOpts.rpVisitFileTimeout < 0) { return { String::format<1024>("Invalid argument to -Z %s", value.constData()), CommandLineParser::Parse_Error }; } if (!serverOpts.rpVisitFileTimeout) serverOpts.rpVisitFileTimeout = -1; break; } case RpIndexerMessageTimeout: { serverOpts.rpIndexDataMessageTimeout = atoi(value.constData()); if (serverOpts.rpIndexDataMessageTimeout <= 0) { return { String::format<1024>("Can't parse argument to -T %s.", value.constData()), CommandLineParser::Parse_Error }; } break; } case RpConnectTimeout: { serverOpts.rpConnectTimeout = atoi(value.constData()); if (serverOpts.rpConnectTimeout < 0) { return { String::format<1024>("Invalid argument to -O %s", value.constData()), CommandLineParser::Parse_Error }; } break; } case RpConnectAttempts: { serverOpts.rpConnectAttempts = atoi(value.constData()); if (serverOpts.rpConnectAttempts <= 0) { return { String::format<1024>("Invalid argument to --rp-connect-attempts %s", value.constData()), CommandLineParser::Parse_Error }; } break; } case RpNiceValue: { bool ok; serverOpts.rpNiceValue = value.toLong(&ok); if (!ok) { return { String::format<1024>("Can't parse argument to -a %s.", value.constData()), CommandLineParser::Parse_Error }; } break; } case SuspendRpOnCrash: { serverOpts.options |= Server::SuspendRPOnCrash; break; } case RpLogToSyslog: { serverOpts.options |= Server::RPLogToSyslog; break; } case StartSuspended: { serverOpts.options |= Server::StartSuspended; break; } case SeparateDebugAndRelease: { serverOpts.options |= Server::SeparateDebugAndRelease; break; } case Separate32BitAnd64Bit: { serverOpts.options |= Server::Separate32BitAnd64Bit; break; } case SourceIgnoreIncludePathDifferencesInUsr: { serverOpts.options |= Server::SourceIgnoreIncludePathDifferencesInUsr; break; } case MaxCrashCount: { serverOpts.maxCrashCount = atoi(value.constData()); if (serverOpts.maxCrashCount <= 0) { return { String::format<1024>("Invalid argument to -K %s", value.constData()), CommandLineParser::Parse_Error }; } break; } case CompletionCacheSize: { serverOpts.completionCacheSize = atoi(value.constData()); if (serverOpts.completionCacheSize <= 0) { return { String::format<1024>("Invalid argument to -i %s", value.constData()), CommandLineParser::Parse_Error }; } break; } case CompletionNoFilter: { serverOpts.options |= Server::CompletionsNoFilter; break; } case CompletionLogs: { serverOpts.options |= Server::CompletionLogs; break; } case MaxIncludeCompletionDepth: { serverOpts.maxIncludeCompletionDepth = strtoul(value.constData(), 0, 10); break; } case AllowWpedantic: { serverOpts.options |= Server::AllowPedantic; break; } case AllowWErrorAndWFatalErrors: { serverOpts.options |= Server::AllowWErrorAndWFatalErrors; break; } case EnableCompilerManager: { serverOpts.options |= Server::EnableCompilerManager; break; } case EnableNDEBUG: { serverOpts.options |= Server::EnableNDEBUG; break; } case Progress: { serverOpts.options |= Server::Progress; break; } case MaxFileMapCacheSize: { serverOpts.maxFileMapScopeCacheSize = atoi(value.constData()); if (serverOpts.maxFileMapScopeCacheSize <= 0) { return { String::format<1024>("Invalid argument to -y %s", value.constData()), CommandLineParser::Parse_Error }; } break; } #ifdef FILEMANAGER_OPT_IN case FileManagerWatch: { serverOpts.options &= ~Server::NoFileManagerWatch; break; } #else case NoFileManagerWatch: { serverOpts.options |= Server::NoFileManagerWatch; break; } #endif case NoFileManager: { serverOpts.options |= Server::NoFileManager; break; } case NoFileLock: { serverOpts.options |= Server::NoFileLock; break; } case PchEnabled: { serverOpts.options |= Server::PCHEnabled; break; } case NoFilesystemWatcher: { serverOpts.options |= Server::NoFileSystemWatch; break; } case ArgTransform: { serverOpts.argTransform = Process::findCommand(value); if (!value.isEmpty() && serverOpts.argTransform.isEmpty()) { return { String::format<1024>("Invalid argument to -V. Can't resolve %s", value.constData()), CommandLineParser::Parse_Error }; } break; } case NoComments: { serverOpts.options |= Server::NoComments; break; } #ifdef RTAGS_HAS_LAUNCHD case Launchd: { serverOpts.options |= Server::Launchd; break; } #endif case InactivityTimeout: { inactivityTimeout = atoi(value.constData()); // seconds. if (inactivityTimeout <= 0) { return { String::format<1024>("Invalid argument to --inactivity-timeout %s", value.constData()), CommandLineParser::Parse_Error }; } break; } case Daemon: { daemon = true; logLevel = LogLevel::None; break; } case LogFileLogLevel: { if (!strcasecmp(value.constData(), "verbose-debug")) { logFileLogLevel = LogLevel::VerboseDebug; } else if (!strcasecmp(value.constData(), "debug")) { logFileLogLevel = LogLevel::Debug; } else if (!strcasecmp(value.constData(), "warning")) { logFileLogLevel = LogLevel::Warning; } else if (!strcasecmp(value.constData(), "error")) { logFileLogLevel = LogLevel::Error; } else { return { String::format<1024>("Unknown log level: %s options are error, warning, debug or verbose-debug", value.constData()), CommandLineParser::Parse_Error }; } break; } case WatchSourcesOnly: { serverOpts.options |= Server::WatchSourcesOnly; break; } case DebugLocations: { if (value == "clear" || value == "none") { serverOpts.debugLocations.clear(); } else { serverOpts.debugLocations << value; } break; } case ValidateFileMaps: { serverOpts.options |= Server::ValidateFileMaps; break; } case TcpPort: { serverOpts.tcpPort = atoi(value.constData()); if (!serverOpts.tcpPort) { return { String::format<1024>("Invalid port %s for --tcp-port", value.constData()), CommandLineParser::Parse_Error }; } break; } case RpPath: { serverOpts.rp = std::move(value); if (serverOpts.rp.isFile()) { serverOpts.rp.resolve(); } else { return { String::format<1024>("%s is not a file", value.constData()), CommandLineParser::Parse_Error }; } break; } case LogTimestamp: { logFlags |= LogTimeStamp; break; } case LogFlushOption: { logFlags |= LogFlush; break; } case SandboxRoot: { serverOpts.sandboxRoot = std::move(value); if (!serverOpts.sandboxRoot.endsWith('/')) serverOpts.sandboxRoot += '/'; if (!serverOpts.sandboxRoot.resolve() || !serverOpts.sandboxRoot.isDir()) { return { String::format<1024>("%s is not a valid directory for sandbox-root", serverOpts.sandboxRoot.constData()), CommandLineParser::Parse_Error }; } break; } case NoRealPath: { Path::setRealPathEnabled(false); serverOpts.options |= Server::NoRealPath; break; } } return { String(), CommandLineParser::Parse_Exec }; }; const std::initializer_list<CommandLineParser::Option<CommandLineParser::ConfigOptionType> > configOpts = { { CommandLineParser::Config, "config", 'c', CommandLineParser::Required, "Use this file (instead of ~/.rdmrc)." }, { CommandLineParser::NoRc, "no-rc", 'N', CommandLineParser::NoValue, "Don't load any rc files." } }; const CommandLineParser::ParseStatus status = CommandLineParser::parse<OptionType>(argc, argv, opts, NullFlags, cb, "rdm", configOpts); switch (status.status) { case CommandLineParser::Parse_Error: fprintf(stderr, "%s\n", status.error.constData()); return 1; case CommandLineParser::Parse_Ok: return 0; case CommandLineParser::Parse_Exec: break; } if (daemon) { switch (fork()) { case -1: fprintf(stderr, "Failed to fork (%d) %s\n", errno, strerror(errno)); return 1; case 0: setsid(); switch (fork()) { case -1: fprintf(stderr, "Failed to fork (%d) %s\n", errno, strerror(errno)); return 1; case 0: break; default: return 0; } break; default: return 0; } } if (serverOpts.excludeFilters.isEmpty()) serverOpts.excludeFilters = String(DEFAULT_EXCLUDEFILTER).split(';'); if (serverOpts.compilerWrappers.isEmpty()) serverOpts.compilerWrappers = String(DEFAULT_COMPILER_WRAPPERS).split(';').toSet(); if (!serverOpts.headerErrorJobCount) { serverOpts.headerErrorJobCount = std::max<size_t>(1, serverOpts.jobCount / 2); } else { serverOpts.headerErrorJobCount = std::min(serverOpts.headerErrorJobCount, serverOpts.jobCount); } if (sigHandler) { signal(SIGSEGV, signalHandler); signal(SIGBUS, signalHandler); signal(SIGILL, signalHandler); signal(SIGABRT, signalHandler); } if (!initLogging(argv[0], logFlags, logLevel, logFile, logFileLogLevel)) { fprintf(stderr, "Can't initialize logging with %d %s %s\n", logLevel.toInt(), logFile.constData(), logFlags.toString().constData()); return 1; } #ifdef RTAGS_HAS_LAUNCHD if (serverOpts.options & Server::Launchd) { // Clamp inactivity timeout. launchd starts to worry if the // process runs for less than 10 seconds. static const int MIN_INACTIVITY_TIMEOUT = 15; // includes // fudge factor. if (inactivityTimeout < MIN_INACTIVITY_TIMEOUT) { inactivityTimeout = MIN_INACTIVITY_TIMEOUT; fprintf(stderr, "launchd mode - clamped inactivity timeout to %d to avoid launchd warnings.\n", inactivityTimeout); } } #endif EventLoop::SharedPtr loop(new EventLoop); loop->init(EventLoop::MainEventLoop|EventLoop::EnableSigIntHandler|EventLoop::EnableSigTermHandler); auto server = std::make_shared<Server>(); if (!serverOpts.tests.isEmpty()) { char buf[1024]; Path path; while (true) { strcpy(buf, "/tmp/rtags-test-XXXXXX"); if (!mkdtemp(buf)) { fprintf(stderr, "Failed to mkdtemp (%d)\n", errno); return 1; } path = buf; path.resolve(); break; } serverOpts.dataDir = path; strcpy(buf, "/tmp/rtags-sock-XXXXXX"); const int fd = mkstemp(buf); if (fd == -1) { fprintf(stderr, "Failed to mkstemp (%d)\n", errno); return 1; } close(fd); serverOpts.socketFile = buf; serverOpts.socketFile.resolve(); } serverOpts.dataDir = serverOpts.dataDir.ensureTrailingSlash(); #ifdef HAVE_BACKTRACE if (strlen(crashDumpFilePath)) { if (crashDumpFilePath[0] != '/') { const String f = crashDumpFilePath; snprintf(crashDumpFilePath, sizeof(crashDumpFilePath), "%s%s", serverOpts.dataDir.constData(), f.constData()); } snprintf(crashDumpTempFilePath, sizeof(crashDumpTempFilePath), "%s.tmp", crashDumpFilePath); Path::mkdir(serverOpts.dataDir); crashDumpFile = fopen(crashDumpTempFilePath, "w"); if (!crashDumpFile) { fprintf(stderr, "Couldn't open temp file %s for write (%d)\n", crashDumpTempFilePath, errno); } } #endif if (!server->init(serverOpts)) { cleanupLogging(); return 1; } if (!serverOpts.tests.isEmpty()) { return server->runTests() ? 0 : 1; } loop->setInactivityTimeout(inactivityTimeout * 1000); loop->exec(); const int ret = server->exitCode(); server.reset(); cleanupLogging(); return ret; }
RClient::ParseStatus RClient::parse(int &argc, char **argv) { Rct::findExecutablePath(*argv); mSocketFile = Path::home() + ".rdm"; List<option> options; options.reserve(sizeof(opts) / sizeof(Option)); List<std::shared_ptr<QueryCommand> > projectCommands; String shortOptionString; Hash<int, Option*> shortOptions, longOptions; for (int i=0; opts[i].description; ++i) { if (opts[i].option != None) { const option opt = { opts[i].longOpt, opts[i].argument, 0, opts[i].shortOpt }; if (opts[i].shortOpt) { shortOptionString.append(opts[i].shortOpt); switch (opts[i].argument) { case no_argument: break; case required_argument: shortOptionString.append(':'); break; case optional_argument: shortOptionString.append("::"); break; } assert(!shortOptions.contains(opts[i].shortOpt)); shortOptions[opts[i].shortOpt] = &opts[i]; } if (opts[i].longOpt) longOptions[options.size()] = &opts[i]; options.push_back(opt); } } if (getenv("RTAGS_DUMP_UNUSED")) { String unused; for (int i=0; i<26; ++i) { if (!shortOptionString.contains('a' + i)) unused.append('a' + i); if (!shortOptionString.contains('A' + i)) unused.append('A' + i); } printf("Unused: %s\n", unused.constData()); for (int i=0; opts[i].description; ++i) { if (opts[i].longOpt) { if (!opts[i].shortOpt) { printf("No shortoption for %s\n", opts[i].longOpt); } else if (opts[i].longOpt[0] != opts[i].shortOpt) { printf("Not ideal option for %s|%c\n", opts[i].longOpt, opts[i].shortOpt); } } } return Parse_Ok; } { const option opt = { 0, 0, 0, 0 }; options.push_back(opt); } Path logFile; Flags<LogFileFlag> logFlags; enum State { Parsing, Done, Error } state = Parsing; while (true) { int idx = -1; const int c = getopt_long(argc, argv, shortOptionString.constData(), options.data(), &idx); switch (c) { case -1: state = Done; break; case '?': case ':': state = Error; break; default: break; } if (state != Parsing) break; const Option *opt = (idx == -1 ? shortOptions.value(c) : longOptions.value(idx)); assert(opt); if (!isatty(STDOUT_FILENO)) { mQueryFlags |= QueryMessage::NoColor; } switch (opt->option) { case None: case NumOptions: assert(0); break; case Help: help(stdout, argv[0]); return Parse_Ok; case Man: man(); return Parse_Ok; case SocketFile: mSocketFile = optarg; break; case GuessFlags: mGuessFlags = true; break; case Wait: mQueryFlags |= QueryMessage::Wait; break; case IMenu: mQueryFlags |= QueryMessage::IMenu; break; case CompilationFlagsOnly: mQueryFlags |= QueryMessage::CompilationFlagsOnly; break; case NoColor: mQueryFlags |= QueryMessage::NoColor; break; case CompilationFlagsSplitLine: mQueryFlags |= QueryMessage::CompilationFlagsSplitLine; break; case ContainingFunction: mQueryFlags |= QueryMessage::ContainingFunction; break; case ContainingFunctionLocation: mQueryFlags |= QueryMessage::ContainingFunctionLocation; break; case DeclarationOnly: mQueryFlags |= QueryMessage::DeclarationOnly; break; case DefinitionOnly: mQueryFlags |= QueryMessage::DefinitionOnly; break; case FindVirtuals: mQueryFlags |= QueryMessage::FindVirtuals; break; case FindFilePreferExact: mQueryFlags |= QueryMessage::FindFilePreferExact; break; case SymbolInfoIncludeParents: mQueryFlags |= QueryMessage::SymbolInfoIncludeParents; break; case SymbolInfoExcludeTargets: mQueryFlags |= QueryMessage::SymbolInfoExcludeTargets; break; case SymbolInfoExcludeReferences: mQueryFlags |= QueryMessage::SymbolInfoExcludeReferences; break; case CursorKind: mQueryFlags |= QueryMessage::CursorKind; break; case SynchronousCompletions: mQueryFlags |= QueryMessage::SynchronousCompletions; break; case DisplayName: mQueryFlags |= QueryMessage::DisplayName; break; case AllReferences: mQueryFlags |= QueryMessage::AllReferences; break; case AllTargets: mQueryFlags |= QueryMessage::AllTargets; break; case MatchCaseInsensitive: mQueryFlags |= QueryMessage::MatchCaseInsensitive; break; case MatchRegex: mQueryFlags |= QueryMessage::MatchRegex; break; case AbsolutePath: mQueryFlags |= QueryMessage::AbsolutePath; break; case ReverseSort: mQueryFlags |= QueryMessage::ReverseSort; break; case Rename: mQueryFlags |= QueryMessage::Rename; break; case Elisp: mQueryFlags |= QueryMessage::Elisp; break; case FilterSystemHeaders: mQueryFlags |= QueryMessage::FilterSystemIncludes; break; case NoContext: mQueryFlags |= QueryMessage::NoContext; break; case PathFilter: { Path p = optarg; p.resolve(); mPathFilters.insert({ p, QueryMessage::PathFilter::Self }); break; } case DependencyFilter: { Path p = optarg; p.resolve(); if (!p.isFile()) { fprintf(stderr, "%s doesn't seem to be a file\n", optarg); return Parse_Error; } mPathFilters.insert({ p, QueryMessage::PathFilter::Dependency }); break; } case KindFilter: mKindFilters.insert(optarg); break; case WildcardSymbolNames: mQueryFlags |= QueryMessage::WildcardSymbolNames; break; case RangeFilter: { char *end; mMinOffset = strtoul(optarg, &end, 10); if (*end != '-') { fprintf(stderr, "Can't parse range, must be uint-uint. E.g. 1-123\n"); return Parse_Error; } mMaxOffset = strtoul(end + 1, &end, 10); if (*end) { fprintf(stderr, "Can't parse range, must be uint-uint. E.g. 1-123\n"); return Parse_Error; } if (mMaxOffset <= mMinOffset || mMinOffset < 0) { fprintf(stderr, "Invalid range (%d-%d), must be uint-uint. E.g. 1-123\n", mMinOffset, mMaxOffset); return Parse_Error; } break; } case Version: fprintf(stdout, "%s\n", RTags::versionString().constData()); return Parse_Ok; case Verbose: ++mLogLevel; break; case PrepareCodeCompleteAt: case CodeCompleteAt: { const String encoded = Location::encode(optarg); if (encoded.isEmpty()) { fprintf(stderr, "Can't resolve argument %s\n", optarg); return Parse_Error; } addQuery(opt->option == CodeCompleteAt ? QueryMessage::CodeCompleteAt : QueryMessage::PrepareCodeCompleteAt, encoded); break; } case Silent: mLogLevel = LogLevel::None; break; case LogFile: logFile = optarg; break; case StripParen: mQueryFlags |= QueryMessage::StripParentheses; break; case DumpIncludeHeaders: mQueryFlags |= QueryMessage::DumpIncludeHeaders; break; case SilentQuery: mQueryFlags |= QueryMessage::SilentQuery; break; case BuildIndex: { bool ok; mBuildIndex = String(optarg).toULongLong(&ok); if (!ok) { fprintf(stderr, "--build-index [arg] must be >= 0\n"); return Parse_Error; } break; } case ConnectTimeout: mConnectTimeout = atoi(optarg); if (mConnectTimeout < 0) { fprintf(stderr, "--connect-timeout [arg] must be >= 0\n"); return Parse_Error; } break; case Max: mMax = atoi(optarg); if (mMax < 0) { fprintf(stderr, "-M [arg] must be >= 0\n"); return Parse_Error; } break; case Timeout: mTimeout = atoi(optarg); if (!mTimeout) { mTimeout = -1; } else if (mTimeout < 0) { fprintf(stderr, "-y [arg] must be >= 0\n"); return Parse_Error; } break; case UnsavedFile: { const String arg(optarg); const int colon = arg.lastIndexOf(':'); if (colon == -1) { fprintf(stderr, "Can't parse -u [%s]\n", optarg); return Parse_Error; } const int bytes = atoi(arg.constData() + colon + 1); if (!bytes) { fprintf(stderr, "Can't parse -u [%s]\n", optarg); return Parse_Error; } const Path path = Path::resolved(arg.left(colon)); if (!path.isFile()) { fprintf(stderr, "Can't open [%s] for reading\n", arg.left(colon).nullTerminated()); return Parse_Error; } String contents(bytes, '\0'); const int r = fread(contents.data(), 1, bytes, stdin); if (r != bytes) { fprintf(stderr, "Read error %d (%s). Got %d, expected %d\n", errno, Rct::strerror(errno).constData(), r, bytes); return Parse_Error; } mUnsavedFiles[path] = contents; break; } case FollowLocation: case SymbolInfo: case ClassHierarchy: case ReferenceLocation: { const String encoded = Location::encode(optarg); if (encoded.isEmpty()) { fprintf(stderr, "Can't resolve argument %s\n", optarg); return Parse_Error; } QueryMessage::Type type = QueryMessage::Invalid; switch (opt->option) { case FollowLocation: type = QueryMessage::FollowLocation; break; case SymbolInfo: type = QueryMessage::SymbolInfo; break; case ReferenceLocation: type = QueryMessage::ReferencesLocation; break; case ClassHierarchy: type = QueryMessage::ClassHierarchy; break; default: assert(0); break; } addQuery(type, encoded, QueryMessage::HasLocation); break; } case CurrentFile: mCurrentFile.append(Path::resolved(optarg)); break; case ReloadFileManager: addQuery(QueryMessage::ReloadFileManager); break; case DumpCompletions: addQuery(QueryMessage::DumpCompletions); break; case DumpCompilationDatabase: addQuery(QueryMessage::DumpCompilationDatabase); break; case Clear: addQuery(QueryMessage::ClearProjects); break; case RdmLog: addLog(RdmLogCommand::Default); break; case Diagnostics: addLog(RTags::Diagnostics); break; case QuitRdm: { const char *arg = 0; if (optarg) { arg = optarg; } else if (optind < argc && argv[optind][0] != '-') { arg = argv[optind++]; } int exit = 0; if (arg) { bool ok; exit = String(arg).toLongLong(&ok); if (!ok) { fprintf(stderr, "Invalid argument to -q\n"); return Parse_Error; } } addQuitCommand(exit); break; } case DeleteProject: addQuery(QueryMessage::DeleteProject, optarg); break; case SendDiagnostics: addQuery(QueryMessage::SendDiagnostics, optarg); break; case FindProjectRoot: { const Path p = Path::resolved(optarg); printf("findProjectRoot [%s] => [%s]\n", p.constData(), RTags::findProjectRoot(p, RTags::SourceRoot).constData()); return Parse_Ok; } case FindProjectBuildRoot: { const Path p = Path::resolved(optarg); printf("findProjectRoot [%s] => [%s]\n", p.constData(), RTags::findProjectRoot(p, RTags::BuildRoot).constData()); return Parse_Ok; } case RTagsConfig: { const Path p = Path::resolved(optarg); Map<String, String> config = RTags::rtagsConfig(p); printf("rtags-config: %s:\n", p.constData()); for (const auto &it : config) { printf("%s: \"%s\"\n", it.first.constData(), it.second.constData()); } return Parse_Ok; } case CurrentProject: addQuery(QueryMessage::Project, String(), QueryMessage::CurrentProjectOnly); break; case CheckReindex: case Reindex: case Project: case FindFile: case ListSymbols: case FindSymbols: case Sources: case IncludeFile: case JobCount: case Status: { Flags<QueryMessage::Flag> extraQueryFlags; QueryMessage::Type type = QueryMessage::Invalid; bool resolve = true; switch (opt->option) { case CheckReindex: type = QueryMessage::CheckReindex; break; case Reindex: type = QueryMessage::Reindex; break; case Project: type = QueryMessage::Project; break; case FindFile: type = QueryMessage::FindFile; resolve = false; break; case Sources: type = QueryMessage::Sources; break; case IncludeFile: type = QueryMessage::IncludeFile; resolve = false; break; case Status: type = QueryMessage::Status; break; case ListSymbols: type = QueryMessage::ListSymbols; break; case FindSymbols: type = QueryMessage::FindSymbols; break; case JobCount: type = QueryMessage::JobCount; break; default: assert(0); break; } const char *arg = 0; if (optarg) { arg = optarg; } else if (optind < argc && argv[optind][0] != '-') { arg = argv[optind++]; } if (arg) { Path p(arg); if (resolve && p.exists()) { p.resolve(); addQuery(type, p, extraQueryFlags); } else { addQuery(type, arg, extraQueryFlags); } } else { addQuery(type, String(), extraQueryFlags); } assert(!mCommands.isEmpty()); if (type == QueryMessage::Project) projectCommands.append(std::static_pointer_cast<QueryCommand>(mCommands.back())); break; } case ListBuffers: addQuery(QueryMessage::SetBuffers); break; case SetBuffers: { const char *arg = 0; if (optarg) { arg = optarg; } else if (optind < argc && (argv[optind][0] != '-' || !strcmp(argv[optind], "-"))) { arg = argv[optind++]; } String encoded; if (arg) { List<Path> paths; auto addBuffer = [&paths](const String &p) { if (p.isEmpty()) return; Path path(p); if (path.resolve() && path.isFile()) { paths.append(path); } else { fprintf(stderr, "\"%s\" doesn't seem to be a file.\n", p.constData()); } }; if (!strcmp(arg, "-")) { char buf[1024]; while (fgets(buf, sizeof(buf), stdin)) { String arg(buf); if (arg.endsWith('\n')) arg.chop(1); addBuffer(arg); } } else { for (const String &buffer : String(arg).split(';')) { addBuffer(buffer); } } Serializer serializer(encoded); serializer << paths; } addQuery(QueryMessage::SetBuffers, encoded); break; } case LoadCompilationDatabase: { #if CLANG_VERSION_MAJOR > 3 || (CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR > 3) Path dir; if (optarg) { dir = optarg; } else if (optind < argc && argv[optind][0] != '-') { dir = argv[optind++]; } else { dir = Path::pwd(); } dir.resolve(Path::MakeAbsolute); if (!dir.exists()) { fprintf(stderr, "%s does not seem to exist\n", dir.constData()); return Parse_Error; } if (!dir.isDir()) { if (dir.isFile() && dir.endsWith("/compile_commands.json")) { dir = dir.parentDir(); } else { fprintf(stderr, "%s is not a directory\n", dir.constData()); return Parse_Error; } } if (!dir.endsWith('/')) dir += '/'; const Path file = dir + "compile_commands.json"; if (!file.isFile()) { fprintf(stderr, "no compile_commands.json file in %s\n", dir.constData()); return Parse_Error; } addCompile(dir, Escape_Auto); #endif break; } case HasFileManager: { Path p; if (optarg) { p = optarg; } else if (optind < argc && argv[optind][0] != '-') { p = argv[optind++]; } else { p = "."; } p.resolve(Path::MakeAbsolute); if (!p.exists()) { fprintf(stderr, "%s does not seem to exist\n", optarg); return Parse_Error; } if (p.isDir()) p.append('/'); addQuery(QueryMessage::HasFileManager, p); break; } case ProjectRoot: { Path p = optarg; if (!p.isDir()) { fprintf(stderr, "%s does not seem to be a directory\n", optarg); return Parse_Error; } p.resolve(Path::MakeAbsolute); mProjectRoot = p; break; } case Suspend: { Path p; if (optarg) { p = optarg; } else if (optind < argc && argv[optind][0] != '-') { p = argv[optind++]; } if (!p.isEmpty()) { if (p != "clear" && p != "all") { p.resolve(Path::MakeAbsolute); if (!p.isFile()) { fprintf(stderr, "%s is not a file\n", optarg); return Parse_Error; } } } addQuery(QueryMessage::Suspend, p); break; } case Compile: { String args = optarg; while (optind < argc) { if (!args.isEmpty()) args.append(' '); args.append(argv[optind++]); } if (args == "-" || args.isEmpty()) { char buf[16384]; while (fgets(buf, sizeof(buf), stdin)) { addCompile(Path::pwd(), buf, Escape_Do); } } else { addCompile(Path::pwd(), args, Escape_Dont); } break; } case IsIndexing: addQuery(QueryMessage::IsIndexing); break; case UnescapeCompileCommands: mEscapeMode = Escape_Do; break; case NoUnescapeCompileCommands: mEscapeMode = Escape_Dont; break; case NoSortReferencesByInput: mQueryFlags |= QueryMessage::NoSortReferencesByInput; break; case IsIndexed: case DumpFile: case CheckIncludes: case GenerateTest: case Diagnose: case FixIts: { Path p = optarg; if (!p.exists()) { fprintf(stderr, "%s does not exist\n", optarg); return Parse_Error; } if (!p.isAbsolute()) p.prepend(Path::pwd()); if (p.isDir()) { if (opt->option != IsIndexed) { fprintf(stderr, "%s is not a file\n", optarg); return Parse_Error; } else if (!p.endsWith('/')) { p.append('/'); } } p.resolve(); Flags<QueryMessage::Flag> extraQueryFlags; QueryMessage::Type type = QueryMessage::Invalid; switch (opt->option) { case GenerateTest: type = QueryMessage::GenerateTest; break; case FixIts: type = QueryMessage::FixIts; break; case DumpFile: type = QueryMessage::DumpFile; break; case CheckIncludes: type = QueryMessage::DumpFile; extraQueryFlags |= QueryMessage::DumpCheckIncludes; break; case Diagnose: type = QueryMessage::Diagnose; break; case IsIndexed: type = QueryMessage::IsIndexed; break; default: assert(0); break; } addQuery(type, p, extraQueryFlags); break; } case AllDependencies: { String encoded; List<String> args; while (optind < argc && argv[optind][0] != '-') { args.append(argv[optind++]); } Serializer s(encoded); s << Path() << args; addQuery(QueryMessage::Dependencies, encoded); break; } case DumpFileMaps: case Dependencies: { Path p = optarg; if (!p.isFile()) { fprintf(stderr, "%s is not a file\n", optarg); return Parse_Error; } p.resolve(); List<String> args; while (optind < argc && argv[optind][0] != '-') { args.append(argv[optind++]); } String encoded; Serializer s(encoded); s << p << args; addQuery(opt->option == DumpFileMaps ? QueryMessage::DumpFileMaps : QueryMessage::Dependencies, encoded); break; } case PreprocessFile: { Path p = optarg; p.resolve(Path::MakeAbsolute); if (!p.isFile()) { fprintf(stderr, "%s is not a file\n", optarg); return Parse_Error; } addQuery(QueryMessage::PreprocessFile, p); break; } case RemoveFile: { const Path p = Path::resolved(optarg, Path::MakeAbsolute); if (!p.exists()) { addQuery(QueryMessage::RemoveFile, p); } else { addQuery(QueryMessage::RemoveFile, optarg); } break; } case ReferenceName: addQuery(QueryMessage::ReferencesName, optarg); break; } } if (state == Error) { help(stderr, argv[0]); return Parse_Error; } if (optind < argc) { fprintf(stderr, "rc: unexpected option -- '%s'\n", argv[optind]); return Parse_Error; } if (!initLogging(argv[0], LogStderr, mLogLevel, logFile, logFlags)) { fprintf(stderr, "Can't initialize logging with %d %s %s\n", mLogLevel.toInt(), logFile.constData(), logFlags.toString().constData()); return Parse_Error; } if (mCommands.isEmpty()) { help(stderr, argv[0]); return Parse_Error; } if (mCommands.size() > projectCommands.size()) { // If there's more than one command one likely does not want output from // the queryCommand (unless there's no arg specified for it). This is so // we don't have to pass a different flag for auto-updating project // using the current buffer but rather piggy-back on --project const int count = projectCommands.size(); for (int i=0; i<count; ++i) { std::shared_ptr<QueryCommand> &cmd = projectCommands[i]; if (!cmd->query.isEmpty()) { cmd->extraQueryFlags |= QueryMessage::Silent; } } } if (!logFile.isEmpty() || mLogLevel > LogLevel::Error) { Log l(LogLevel::Warning); l << argc; for (int i = 0; i < argc; ++i) l << " " << argv[i]; } mArgc = argc; mArgv = argv; return Parse_Exec; }