|
17 | 17 | package cmd |
18 | 18 |
|
19 | 19 | import ( |
| 20 | + "slices" |
20 | 21 | "bufio" |
| 22 | + "bytes" |
21 | 23 | "context" |
22 | 24 | "encoding/json" |
23 | 25 | "errors" |
@@ -517,13 +519,10 @@ func callFlatpakSessionHelper(container podman.Container) error { |
517 | 519 | var needsFlatpakSessionHelper bool |
518 | 520 |
|
519 | 521 | mounts := container.Mounts() |
520 | | - for _, mount := range mounts { |
521 | | - if mount == "/run/host/monitor" { |
| 522 | + if slices.Contains(mounts, "/run/host/monitor") { |
522 | 523 | logrus.Debug("Requires org.freedesktop.Flatpak.SessionHelper") |
523 | 524 | needsFlatpakSessionHelper = true |
524 | | - break |
525 | 525 | } |
526 | | - } |
527 | 526 |
|
528 | 527 | if !needsFlatpakSessionHelper { |
529 | 528 | return nil |
@@ -1110,3 +1109,217 @@ func (err *entryPointError) Is(target error) bool { |
1110 | 1109 | targetErrMsg := target.Error() |
1111 | 1110 | return err.msg == targetErrMsg |
1112 | 1111 | } |
| 1112 | + |
| 1113 | +func runCommandWithOutput(container string, |
| 1114 | + defaultContainer bool, |
| 1115 | + image, release string, |
| 1116 | + preserveFDs uint, |
| 1117 | + command []string, |
| 1118 | + emitEscapeSequence, fallbackToBash, pedantic bool) (string, error) { |
| 1119 | + |
| 1120 | + if !pedantic { |
| 1121 | + if image == "" { |
| 1122 | + panic("image not specified") |
| 1123 | + } |
| 1124 | + if release == "" { |
| 1125 | + panic("release not specified") |
| 1126 | + } |
| 1127 | + } |
| 1128 | + |
| 1129 | + logrus.Debugf("Checking if container %s exists", container) |
| 1130 | + |
| 1131 | + if _, err := podman.ContainerExists(container); err != nil { |
| 1132 | + logrus.Debugf("Container %s not found", container) |
| 1133 | + |
| 1134 | + if pedantic { |
| 1135 | + return "", createErrorContainerNotFound(container) |
| 1136 | + } |
| 1137 | + |
| 1138 | + containers, err := getContainers() |
| 1139 | + if err != nil { |
| 1140 | + return "", createErrorContainerNotFound(container) |
| 1141 | + } |
| 1142 | + |
| 1143 | + if len(containers) == 0 { |
| 1144 | + shouldCreate := rootFlags.assumeYes || askForConfirmation("No Toolbx containers found. Create now? [y/N]") |
| 1145 | + if !shouldCreate { |
| 1146 | + fmt.Printf("A container can be created later with the 'create' command.\n") |
| 1147 | + fmt.Printf("Run '%s --help' for usage.\n", executableBase) |
| 1148 | + return "", nil |
| 1149 | + } |
| 1150 | + if err := createContainer(container, image, release, "", false); err != nil { |
| 1151 | + return "", err |
| 1152 | + } |
| 1153 | + } else if len(containers) == 1 && defaultContainer { |
| 1154 | + fmt.Fprintf(os.Stderr, "Error: container %s not found\n", container) |
| 1155 | + container = containers[0].Name() |
| 1156 | + fmt.Fprintf(os.Stderr, "Entering container %s instead.\n", container) |
| 1157 | + fmt.Fprintf(os.Stderr, "Use the 'create' command to create a different Toolbx.\n") |
| 1158 | + fmt.Fprintf(os.Stderr, "Run '%s --help' for usage.\n", executableBase) |
| 1159 | + } else { |
| 1160 | + return "", fmt.Errorf("container %s not found\nUse the '--container' option to select a Toolbx.\nRun '%s --help' for usage.", container, executableBase) |
| 1161 | + } |
| 1162 | + } |
| 1163 | + |
| 1164 | + containerObj, err := podman.InspectContainer(container) |
| 1165 | + if err != nil { |
| 1166 | + return "", fmt.Errorf("failed to inspect container %s", container) |
| 1167 | + } |
| 1168 | + |
| 1169 | + if containerObj.EntryPoint() != "toolbox" { |
| 1170 | + return "", fmt.Errorf("container %s is too old and no longer supported\nRecreate it with Toolbx version 0.0.17 or newer.", container) |
| 1171 | + } |
| 1172 | + |
| 1173 | + if err := callFlatpakSessionHelper(containerObj); err != nil { |
| 1174 | + return "", err |
| 1175 | + } |
| 1176 | + |
| 1177 | + var cdiEnviron []string |
| 1178 | + cdiSpec, err := nvidia.GenerateCDISpec() |
| 1179 | + if err != nil { |
| 1180 | + if errors.Is(err, nvidia.ErrNVMLDriverLibraryVersionMismatch) { |
| 1181 | + return "", fmt.Errorf("the proprietary NVIDIA driver's kernel and user space don't match\nCheck the host operating system and systemd journal.") |
| 1182 | + } else if !errors.Is(err, nvidia.ErrPlatformUnsupported) { |
| 1183 | + return "", err |
| 1184 | + } |
| 1185 | + } else { |
| 1186 | + cdiEnviron = append(cdiEnviron, cdiSpec.ContainerEdits.Env...) |
| 1187 | + } |
| 1188 | + |
| 1189 | + p11Environ, err := startP11KitServer() |
| 1190 | + if err != nil { |
| 1191 | + return "", err |
| 1192 | + } |
| 1193 | + |
| 1194 | + entryPointPID := containerObj.EntryPointPID() |
| 1195 | + startTime := time.Unix(-1, 0) |
| 1196 | + |
| 1197 | + if entryPointPID <= 0 { |
| 1198 | + if cdiSpec != nil { |
| 1199 | + cdiFile, err := getCDIFileForNvidia(currentUser) |
| 1200 | + if err != nil { |
| 1201 | + return "", err |
| 1202 | + } |
| 1203 | + if err := saveCDISpecTo(cdiSpec, cdiFile); err != nil { |
| 1204 | + return "", err |
| 1205 | + } |
| 1206 | + } |
| 1207 | + |
| 1208 | + startTime = time.Now() |
| 1209 | + if err := startContainer(container); err != nil { |
| 1210 | + return "", err |
| 1211 | + } |
| 1212 | + |
| 1213 | + containerObj, err = podman.InspectContainer(container) |
| 1214 | + if err != nil { |
| 1215 | + return "", fmt.Errorf("failed to inspect container %s", container) |
| 1216 | + } |
| 1217 | + |
| 1218 | + entryPointPID = containerObj.EntryPointPID() |
| 1219 | + if entryPointPID <= 0 { |
| 1220 | + if err := showEntryPointLogs(container, startTime); err != nil { |
| 1221 | + logrus.Debugf("Reading logs from container %s failed: %s", container, err) |
| 1222 | + } |
| 1223 | + return "", fmt.Errorf("invalid entry point PID of container %s", container) |
| 1224 | + } |
| 1225 | + } |
| 1226 | + |
| 1227 | + if err := ensureContainerIsInitialized(container, entryPointPID, startTime); err != nil { |
| 1228 | + return "", err |
| 1229 | + } |
| 1230 | + |
| 1231 | + environ := append(cdiEnviron, p11Environ...) |
| 1232 | + return runCommandWithFallbacksWithOutput(container, preserveFDs, command, environ, emitEscapeSequence, fallbackToBash) |
| 1233 | + } |
| 1234 | + |
| 1235 | + func runCommandWithFallbacksWithOutput(container string, |
| 1236 | + preserveFDs uint, |
| 1237 | + command, environ []string, |
| 1238 | + emitEscapeSequence, fallbackToBash bool) (string, error) { |
| 1239 | + |
| 1240 | + detachKeysSupported := podman.CheckVersion("1.8.1") |
| 1241 | + |
| 1242 | + envOptions := utils.GetEnvOptionsForPreservedVariables() |
| 1243 | + for _, env := range environ { |
| 1244 | + envOptions = append(envOptions, "--env="+env) |
| 1245 | + } |
| 1246 | + |
| 1247 | + preserveFDsString := fmt.Sprint(preserveFDs) |
| 1248 | + var ttyNeeded bool |
| 1249 | + var stdout, stderr bytes.Buffer |
| 1250 | + |
| 1251 | + if term.IsTerminal(os.Stdin) && term.IsTerminal(os.Stdout) { |
| 1252 | + ttyNeeded = true |
| 1253 | + } |
| 1254 | + |
| 1255 | + workDir := workingDirectory |
| 1256 | + cmdIdx, dirIdx := 0, 0 |
| 1257 | + |
| 1258 | + for { |
| 1259 | + execArgs := constructExecArgs(container, |
| 1260 | + preserveFDsString, |
| 1261 | + command, |
| 1262 | + detachKeysSupported, |
| 1263 | + envOptions, |
| 1264 | + fallbackToBash, |
| 1265 | + ttyNeeded, |
| 1266 | + workDir) |
| 1267 | + |
| 1268 | + if emitEscapeSequence { |
| 1269 | + fmt.Printf("\033]777;container;push;%s;toolbox;%s\033\\", container, currentUser.Uid) |
| 1270 | + } |
| 1271 | + |
| 1272 | + stdout.Reset() |
| 1273 | + stderr.Reset() |
| 1274 | + |
| 1275 | + exitCode, err := shell.RunWithExitCode("podman", os.Stdin, &stdout, &stderr, execArgs...) |
| 1276 | + |
| 1277 | + if emitEscapeSequence { |
| 1278 | + fmt.Printf("\033]777;container;pop;;;%s\033\\", currentUser.Uid) |
| 1279 | + } |
| 1280 | + |
| 1281 | + switch exitCode { |
| 1282 | + case 0: |
| 1283 | + if err != nil { |
| 1284 | + panic("unexpected error: 'podman exec' succeeded but returned error") |
| 1285 | + } |
| 1286 | + return stdout.String(), nil |
| 1287 | + case 125: |
| 1288 | + return "", &exitError{exitCode, fmt.Errorf("failed to invoke 'podman exec' in container %s", container)} |
| 1289 | + case 126: |
| 1290 | + return "", &exitError{exitCode, fmt.Errorf("failed to invoke command %s in container %s", command[0], container)} |
| 1291 | + case 127: |
| 1292 | + if ok, _ := isPathPresent(container, workDir); !ok { |
| 1293 | + if dirIdx < len(runFallbackWorkDirs) { |
| 1294 | + fmt.Fprintf(os.Stderr, "Error: directory %s not found in container %s\n", workDir, container) |
| 1295 | + workDir = runFallbackWorkDirs[dirIdx] |
| 1296 | + if workDir == "" { |
| 1297 | + workDir = getCurrentUserHomeDir() |
| 1298 | + } |
| 1299 | + fmt.Fprintf(os.Stderr, "Using %s instead.\n", workDir) |
| 1300 | + dirIdx++ |
| 1301 | + continue |
| 1302 | + } |
| 1303 | + return "", &exitError{exitCode, fmt.Errorf("directory %s not found in container %s", workDir, container)} |
| 1304 | + } |
| 1305 | + |
| 1306 | + if _, err := isCommandPresent(container, command[0]); err != nil { |
| 1307 | + if fallbackToBash && cmdIdx < len(runFallbackCommands) { |
| 1308 | + fmt.Fprintf(os.Stderr, "Error: command %s not found in container %s\n", command[0], container) |
| 1309 | + command = runFallbackCommands[cmdIdx] |
| 1310 | + fmt.Fprintf(os.Stderr, "Using %s instead.\n", command[0]) |
| 1311 | + cmdIdx++ |
| 1312 | + continue |
| 1313 | + } |
| 1314 | + return "", &exitError{exitCode, fmt.Errorf("command %s not found in container %s", command[0], container)} |
| 1315 | + } |
| 1316 | + |
| 1317 | + if command[0] == "toolbox" { |
| 1318 | + return "", &exitError{exitCode, nil} |
| 1319 | + } |
| 1320 | + return stdout.String(), nil |
| 1321 | + default: |
| 1322 | + return "", &exitError{exitCode, nil} |
| 1323 | + } |
| 1324 | + } |
| 1325 | + } |
0 commit comments