master
cpp 197 lines 6.25 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 Main.cpp
8
9 Abstract:
10
11 Main program entry point.
12
13 --*/
14 #define WIN32_LEAN_AND_MEAN
15 #pragma once
16 #include <Windows.h>
17 #include "precomp.h"
18 #include "wslutil.h"
19 #include "Errors.h"
20 #include "CLIExecutionContext.h"
21 #include "EnvironmentOptions.h"
22 #include "Invocation.h"
23 #include "RootCommand.h"
24
25 using namespace wsl::shared;
26 using namespace wsl::windows::common;
27 using namespace wsl::windows::wslc::execution;
28
29 namespace wsl::windows::wslc {
30 int CoreMain(int argc, wchar_t const** argv)
31 try
32 {
33 EnableContextualizedErrors(false, true);
34 HRESULT result = S_OK;
35
36 wslutil::ConfigureCrt();
37 wslutil::InitializeWil();
38
39 // Extract the executable name from argv[0] for use in help/usage output.
40 if (argc > 0 && argv[0])
41 {
42 std::wstring_view exe{argv[0]};
43 auto lastSlash = exe.find_last_of(L"\\/");
44 if (lastSlash != std::wstring_view::npos)
45 {
46 exe = exe.substr(lastSlash + 1);
47 }
48 auto dot = exe.rfind(L'.');
49 if (dot != std::wstring_view::npos)
50 {
51 exe = exe.substr(0, dot);
52 }
53 s_ExecutableName = exe;
54 }
55
56 WslTraceLoggingInitialize(WslcTelemetryProvider, !wsl::shared::OfficialBuild);
57 auto cleanupTelemetry = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { WslTraceLoggingUninitialize(); });
58
59 wslutil::SetCrtEncoding(_O_U8TEXT);
60 auto coInit = wil::CoInitializeEx(COINIT_MULTITHREADED);
61 wslutil::CoInitializeSecurity();
62
63 // Must be declared after COM init; it holds COM references.
64 CLIExecutionContext context;
65
66 // SetConsoleCtrlHandler only accepts plain function pointers, so route Ctrl-C
67 // through a static reference into the context.
68 static auto& s_cancelEvent = context.CancelEvent;
69 auto ctrlHandler = [](DWORD ctrlType) -> BOOL {
70 if (ctrlType == CTRL_C_EVENT || ctrlType == CTRL_BREAK_EVENT)
71 {
72 if (s_cancelEvent && !s_cancelEvent.is_signaled())
73 {
74 s_cancelEvent.SetEvent();
75 return TRUE;
76 }
77 }
78 return FALSE;
79 };
80 SetConsoleCtrlHandler(ctrlHandler, TRUE);
81 auto unregisterHandler = wil::scope_exit([&]() { SetConsoleCtrlHandler(ctrlHandler, FALSE); });
82
83 WSADATA data{};
84 THROW_IF_WIN32_ERROR(WSAStartup(MAKEWORD(2, 2), &data));
85 auto wsaCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { WSACleanup(); });
86
87 std::unique_ptr<Command> command = std::make_unique<RootCommand>();
88
89 // Environment variable scanning.
90 // The env-bound argument set is the only state needed before NO_COLOR is
91 // applied; keep just this and the noexcept env apply outside the try so a
92 // throw can't reroute through the colored-help error path.
93 auto envDefs = command->GetGlobalsAndEnvArguments();
94 ApplyEnvironmentOptions(context.GlobalArgs, envDefs);
95 context.ApplyGlobalEnvironmentOptions();
96
97 // Past this point, environment variable options are in effect.
98
99 try
100 {
101 std::vector<std::wstring> args;
102 for (int i = 1; i < argc; ++i)
103 {
104 args.emplace_back(argv[i]);
105 }
106
107 Invocation invocation{std::move(args)};
108
109 // Pass 1 — CLI globals. Consume only the global options we recognize at
110 // the front of the invocation; anything else (subcommands, unknown
111 // options, --help, --version, malformed tokens) is left in place for
112 // the regular pipeline to parse and report against the right command.
113 auto cliGlobals = command->GetGlobalArguments();
114 command->ParseArguments(
115 invocation,
116 context.GlobalArgs,
117 cliGlobals,
118 /*optionsOnly*/ true,
119 /*stopOnUnknown*/ true,
120 /*overridableDefaults*/ envDefs);
121 command->ValidateArguments(context.GlobalArgs, envDefs, /*runInternalHook*/ false);
122
123 // Past this point, global option parsing and validation are complete.
124
125 // Pass 2 - Subcommand and leaf command resolution.
126 std::unique_ptr<Command> subCommand = command->FindSubCommand(invocation);
127 while (subCommand)
128 {
129 command = std::move(subCommand);
130 subCommand = command->FindSubCommand(invocation);
131 }
132
133 command->ParseArguments(invocation, context.Args);
134 command->ValidateArguments(context.Args);
135 command->Execute(context);
136 }
137 catch (const ArgumentException& ae)
138 {
139 command->OutputHelp(context.Terminal, HelpOutput::Argument, &ae, ae.Arguments());
140 return 1;
141 }
142 catch (const CommandException& ce)
143 {
144 command->OutputHelp(context.Terminal, HelpOutput::Command, &ce);
145 return 1;
146 }
147 catch (const ExecutionException& ee)
148 {
149 context.Terminal.Error(L"{}\n", ee.Message());
150 return 1;
151 }
152 catch (...)
153 {
154 LOG_CAUGHT_EXCEPTION();
155
156 // If the user pressed Ctrl-C, acknowledge the cancellation and exit.
157 if (context.CancelEvent && context.CancelEvent.is_signaled())
158 {
159 // Cancel events are often considered warnings rather than errors, as the user
160 // intentionally triggered it.
161 const auto strings = wslutil::ErrorToString({.Code = HRESULT_FROM_WIN32(ERROR_CANCELLED)});
162 context.Terminal.Warn(L"\n{}\n", strings.Message);
163
164 // Exit with code 1 is consistent with Docker build and pull, but the POSIX-convention
165 // for cancellation is exit code 130, which is used by Docker compose and most shells.
166 // TODO: Consider switching to 130 or differentiate the cancellation types when we have
167 // more than image cancellation supported.
168 return 1;
169 }
170
171 // Using WSL shared utility to get the HRESULT from the caught exception.
172 // CLIExecutionContext is a derived class of wsl::windows::common::ExecutionContext.
173 result = wil::ResultFromCaughtException();
174
175 if (FAILED(result))
176 {
177 context.ReportError(result);
178 }
179 }
180
181 if (context.ExitCode.has_value())
182 {
183 return context.ExitCode.value();
184 }
185
186 return FAILED(result) ? 1 : 0;
187 }
188 catch (...)
189 {
190 return 1;
191 }
192 } // namespace wsl::windows::wslc
193
194 int wmain(int argc, wchar_t const** argv)
195 {
196 return wsl::windows::wslc::CoreMain(argc, argv);
197 }