| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | VolumeTasks.cpp |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | Implementation of volume command related execution logic. |
| 12 | |
| 13 | --*/ |
| 14 | #include "Argument.h" |
| 15 | #include "ArgumentConvertedTypes.h" |
| 16 | #include "CLIExecutionContext.h" |
| 17 | #include "VolumeModel.h" |
| 18 | #include "VolumeService.h" |
| 19 | #include "VolumeTasks.h" |
| 20 | #include "TableOutput.h" |
| 21 | #include <wslc_schema.h> |
| 22 | |
| 23 | using namespace wsl::shared; |
| 24 | using namespace wsl::windows::common; |
| 25 | using namespace wsl::windows::common::string; |
| 26 | using namespace wsl::windows::common::wslutil; |
| 27 | using namespace wsl::windows::wslc::execution; |
| 28 | using namespace wsl::windows::wslc::models; |
| 29 | using namespace wsl::windows::wslc::services; |
| 30 | using wsl::windows::common::string::FormatHumanReadableSize; |
| 31 | |
| 32 | namespace wsl::windows::wslc::task { |
| 33 | |
| 34 | namespace { |
| 35 | |
| 36 | // Reported for the fields that only carry a value when volume usage data or swarm cluster |
| 37 | // information is available, neither of which applies here. |
| 38 | constexpr std::string_view c_volumeNotAvailable = "N/A"; |
| 39 | |
| 40 | // Converts session volume entries into the all-string shape used for "volume list --format json". |
| 41 | VolumeOutputInformation ToVolumeOutput(const wslc_schema::VolumeListEntry& volume) |
| 42 | { |
| 43 | VolumeOutputInformation entry; |
| 44 | entry.Availability = c_volumeNotAvailable; |
| 45 | entry.Driver = volume.Driver; |
| 46 | entry.Group = c_volumeNotAvailable; |
| 47 | entry.Links = c_volumeNotAvailable; |
| 48 | entry.Mountpoint = volume.Mountpoint; |
| 49 | entry.Name = volume.Name; |
| 50 | entry.Scope = volume.Scope; |
| 51 | entry.Size = c_volumeNotAvailable; |
| 52 | entry.Status = c_volumeNotAvailable; |
| 53 | |
| 54 | for (const auto& [key, value] : volume.Labels) |
| 55 | { |
| 56 | if (!entry.Labels.empty()) |
| 57 | { |
| 58 | entry.Labels += ","; |
| 59 | } |
| 60 | |
| 61 | entry.Labels += std::format("{}={}", key, value); |
| 62 | } |
| 63 | |
| 64 | return entry; |
| 65 | } |
| 66 | |
| 67 | } // namespace |
| 68 | |
| 69 | static bool TryInspectVolume(Terminal& terminal, Session& session, const std::string& volumeName, std::optional<wslc_schema::InspectVolume>& inspectData) |
| 70 | { |
| 71 | try |
| 72 | { |
| 73 | inspectData = VolumeService::Inspect(session, volumeName); |
| 74 | return true; |
| 75 | } |
| 76 | catch (const wil::ResultException& ex) |
| 77 | { |
| 78 | if (ex.GetErrorCode() == WSLC_E_VOLUME_NOT_FOUND) |
| 79 | { |
| 80 | terminal.Error(L"{}\n", Localization::MessageWslcVolumeNotFound(volumeName.c_str())); |
| 81 | return false; |
| 82 | } |
| 83 | |
| 84 | throw; |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | static bool TryDeleteVolume(Terminal& terminal, Session& session, const std::string& volumeName, bool force) |
| 89 | { |
| 90 | try |
| 91 | { |
| 92 | VolumeService::Delete(session, volumeName); |
| 93 | return true; |
| 94 | } |
| 95 | catch (const wil::ResultException& ex) |
| 96 | { |
| 97 | if (ex.GetErrorCode() == WSLC_E_VOLUME_NOT_FOUND) |
| 98 | { |
| 99 | if (!force) |
| 100 | { |
| 101 | terminal.Error(L"{}\n", Localization::MessageWslcVolumeNotFound(volumeName.c_str())); |
| 102 | } |
| 103 | |
| 104 | return false; |
| 105 | } |
| 106 | |
| 107 | throw; |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | void CreateVolume(CLIExecutionContext& context) |
| 112 | { |
| 113 | WI_ASSERT(context.Data.Contains(Data::Session)); |
| 114 | |
| 115 | models::CreateVolumeOptions options{}; |
| 116 | if (context.Args.Contains(ArgType::VolumeName)) |
| 117 | { |
| 118 | options.Name = WideToMultiByte(context.Args.GetValue<ArgType::VolumeName>()); |
| 119 | } |
| 120 | |
| 121 | for (const auto& option : context.Args.GetAllValues<ArgType::Options>()) |
| 122 | { |
| 123 | options.DriverOpts.push_back(option); |
| 124 | } |
| 125 | |
| 126 | for (const auto& label : context.Args.GetAllValues<ArgType::Label>()) |
| 127 | { |
| 128 | options.Labels.push_back(label); |
| 129 | } |
| 130 | |
| 131 | if (context.Args.Contains(ArgType::Driver)) |
| 132 | { |
| 133 | options.Driver = WideToMultiByte(context.Args.GetValue<ArgType::Driver>()); |
| 134 | } |
| 135 | |
| 136 | auto result = VolumeService::Create(context.Data.Get<Data::Session>(), options); |
| 137 | context.Terminal.Output(L"{}\n", MultiByteToWide(result.Name)); |
| 138 | } |
| 139 | |
| 140 | void DeleteVolumes(CLIExecutionContext& context) |
| 141 | { |
| 142 | WI_ASSERT(context.Data.Contains(Data::Session)); |
| 143 | auto& session = context.Data.Get<Data::Session>(); |
| 144 | auto volumeNames = context.Args.GetAllValues<ArgType::VolumeName>(); |
| 145 | const bool force = context.Args.GetValue<ArgType::Force>(); |
| 146 | for (const auto& name : volumeNames) |
| 147 | { |
| 148 | if (TryDeleteVolume(context.Terminal, session, WideToMultiByte(name), force)) |
| 149 | { |
| 150 | context.Terminal.Output(L"{}\n", name); |
| 151 | } |
| 152 | else if (!force) |
| 153 | { |
| 154 | context.ExitCode = 1; |
| 155 | } |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | void GetVolumes(CLIExecutionContext& context) |
| 160 | { |
| 161 | WI_ASSERT(context.Data.Contains(Data::Session)); |
| 162 | auto& session = context.Data.Get<Data::Session>(); |
| 163 | auto filters = context.Args.GetAllValues<ArgType::Filter>(); |
| 164 | context.Data.Add<Data::Volumes>(VolumeService::List(session, filters)); |
| 165 | } |
| 166 | |
| 167 | void InspectVolumes(CLIExecutionContext& context) |
| 168 | { |
| 169 | WI_ASSERT(context.Data.Contains(Data::Session)); |
| 170 | auto& session = context.Data.Get<Data::Session>(); |
| 171 | auto volumeNames = context.Args.GetAllValues<ArgType::VolumeName>(); |
| 172 | std::vector<wsl::windows::common::wslc_schema::InspectVolume> result; |
| 173 | for (const auto& name : volumeNames) |
| 174 | { |
| 175 | std::optional<wslc_schema::InspectVolume> inspectData; |
| 176 | if (TryInspectVolume(context.Terminal, session, WideToMultiByte(name), inspectData)) |
| 177 | { |
| 178 | result.push_back(*inspectData); |
| 179 | } |
| 180 | else |
| 181 | { |
| 182 | context.ExitCode = 1; |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | auto json = ToJson(result, context.Args.GetValue<ArgType::InspectFormat>(c_jsonPrettyPrintIndent)); |
| 187 | context.Terminal.Output(L"{}\n", MultiByteToWide(json)); |
| 188 | } |
| 189 | |
| 190 | void ListVolumes(CLIExecutionContext& context) |
| 191 | { |
| 192 | WI_ASSERT(context.Data.Contains(Data::Volumes)); |
| 193 | auto& volumes = context.Data.Get<Data::Volumes>(); |
| 194 | |
| 195 | if (context.Args.GetValue<ArgType::Quiet>()) |
| 196 | { |
| 197 | for (const auto& volume : volumes) |
| 198 | { |
| 199 | context.Terminal.Output(L"{}\n", MultiByteToWide(volume.Name)); |
| 200 | } |
| 201 | |
| 202 | return; |
| 203 | } |
| 204 | |
| 205 | const auto format = context.Args.GetValue<ArgType::Format>(FormatType::Table); |
| 206 | |
| 207 | switch (format) |
| 208 | { |
| 209 | case FormatType::Json: |
| 210 | { |
| 211 | for (const auto& volume : volumes) |
| 212 | { |
| 213 | context.Terminal.Output(L"{}\n", ToJsonW(ToVolumeOutput(volume), c_jsonCompactIndent)); |
| 214 | } |
| 215 | |
| 216 | break; |
| 217 | } |
| 218 | case FormatType::Table: |
| 219 | { |
| 220 | auto table = wsl::windows::wslc::TableOutput<2>(context.Terminal, {L"DRIVER", L"VOLUME NAME"}); |
| 221 | for (const auto& volume : volumes) |
| 222 | { |
| 223 | table.WriteRow({ |
| 224 | MultiByteToWide(volume.Driver), |
| 225 | MultiByteToWide(volume.Name), |
| 226 | }); |
| 227 | } |
| 228 | |
| 229 | table.Complete(); |
| 230 | break; |
| 231 | } |
| 232 | default: |
| 233 | THROW_HR(E_UNEXPECTED); |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | void PruneVolumes(CLIExecutionContext& context) |
| 238 | { |
| 239 | WI_ASSERT(context.Data.Contains(Data::Session)); |
| 240 | auto& session = context.Data.Get<Data::Session>(); |
| 241 | |
| 242 | const bool all = context.Args.GetValue<ArgType::All>(); |
| 243 | |
| 244 | // Filter values are parsed and cached during argument validation. |
| 245 | auto filters = context.Args.GetAllValues<ArgType::Filter>(); |
| 246 | |
| 247 | auto result = VolumeService::Prune(context.Terminal, session, all, filters); |
| 248 | |
| 249 | if (!result.PrunedVolumes.empty()) |
| 250 | { |
| 251 | context.Terminal.Output(L"{}\n", Localization::WSLCCLI_VolumePruneDeletedHeader()); |
| 252 | for (const auto& volumeName : result.PrunedVolumes) |
| 253 | { |
| 254 | context.Terminal.Output(L"{}\n", MultiByteToWide(volumeName)); |
| 255 | } |
| 256 | |
| 257 | context.Terminal.Output(L"\n"); |
| 258 | } |
| 259 | |
| 260 | context.Terminal.Output( |
| 261 | L"{}\n", Localization::WSLCCLI_VolumePruneSpaceReclaimed(FormatHumanReadableSize(result.SpaceReclaimed, c_reclaimedSpacePrecision))); |
| 262 | } |
| 263 | } // namespace wsl::windows::wslc::task |