<commit_before>/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch licenses this file to you under
 * the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.elasticsearch.packaging.util;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.SuppressForbidden;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Wrapper to run shell commands and collect their outputs in a less verbose way
 */
public class Shell {

    public static final int TAIL_WHEN_TOO_MUCH_OUTPUT = 1000;
    protected final Logger logger =  LogManager.getLogger(getClass());

    final Map<String, String> env = new HashMap<>();
    Path workingDirectory;

    public Shell() {
        this.workingDirectory = null;
    }

    public Map<String, String> getEnv() {
        return env;
    }

    public void setWorkingDirectory(Path workingDirectory) {
        this.workingDirectory = workingDirectory;
    }

    /**
     * Run the provided string as a shell script. On Linux the {@code bash -c [script]} syntax will be used, and on Windows
     * the {@code powershell.exe -Command [script]} syntax will be used. Throws an exception if the exit code of the script is nonzero
     */
    public Result run(String script) {
        return runScript(getScriptCommand(script));
    }

    /**
     * Same as {@link #run(String)}, but does not throw an exception if the exit code of the script is nonzero
     */
    public Result runIgnoreExitCode(String script) {
        return runScriptIgnoreExitCode(getScriptCommand(script));
    }

    public void chown(Path path) throws Exception {
        Platforms.onLinux(() -> run("chown -R elasticsearch:elasticsearch " + path));
        Platforms.onWindows(() -> run(
            "$account = New-Object System.Security.Principal.NTAccount '" + System.getenv("username")  + "'; " +
                "$tempConf = Get-ChildItem '" + path + "' -Recurse; " +
                "$tempConf += Get-Item '" + path + "'; " +
                "$tempConf | ForEach-Object { " +
                "$acl = Get-Acl $_.FullName; " +
                "$acl.SetOwner($account); " +
                "Set-Acl $_.FullName $acl " +
                "}"
        ));
    }

    public Result run( String command, Object... args) {
        String formattedCommand = String.format(Locale.ROOT, command, args);
        return run(formattedCommand);
    }
    private String[] getScriptCommand(String script) {
        if (Platforms.WINDOWS) {
            return powershellCommand(script);
        } else {
            return bashCommand(script);
        }
    }

    private static String[] bashCommand(String script) {
        return Stream.concat(Stream.of("bash", "-c"), Stream.of(script)).toArray(String[]::new);
    }

    private static String[] powershellCommand(String script) {
        return Stream.concat(Stream.of("powershell.exe", "-Command"), Stream.of(script)).toArray(String[]::new);
    }

    private Result runScript(String[] command) {
        Result result = runScriptIgnoreExitCode(command);
        if (result.isSuccess() == false) {
            throw new RuntimeException("Command was not successful: [" + String.join(" ", command) + "]\n   result: " + result.toString());
        }
        return result;
    }

    private Result runScriptIgnoreExitCode(String[] command) {
        ProcessBuilder builder = new ProcessBuilder();
        builder.command(command);
        if (workingDirectory != null) {
            setWorkingDirectory(builder, workingDirectory);
        }
        for (Map.Entry<String, String> entry : env.entrySet()) {
            builder.environment().put(entry.getKey(), entry.getValue());
        }
        final Path stdOut;
        final Path stdErr;
        try {
            Path tmpDir = Paths.get(System.getProperty("java.io.tmpdir"));
            Files.createDirectories(tmpDir);
            stdOut = Files.createTempFile(tmpDir, getClass().getName(), ".out");
            stdErr = Files.createTempFile(tmpDir, getClass().getName(), ".err");
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }

        redirectOutAndErr(builder, stdOut, stdErr);

        try {
            Process process = builder.start();
            if (process.waitFor(10, TimeUnit.MINUTES) == false) {
                if (process.isAlive()) {
                    process.destroyForcibly();
                }
                Result result = new Result(
                    -1,
                    readFileIfExists(stdOut),
                    readFileIfExists(stdErr)
                );
                throw new IllegalStateException(
                    "Timed out running shell command: " + command + "\n" +
                    "Result:\n" + result
                );
            }

            Result result = new Result(
                process.exitValue(),
                readFileIfExists(stdOut),
                readFileIfExists(stdErr)
            );
            logger.info("Ran: {} {}", Arrays.toString(command), result);
            return result;

        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        } finally {
           FileUtils.deleteIfExists(stdOut);
           FileUtils.deleteIfExists(stdErr);
        }
    }

    private String readFileIfExists(Path path) throws IOException {
        if (Files.exists(path)) {
            long size = Files.size(path);
            if (size > 100 * 1024) {
                return "<<Too large to read: " + size  + " bytes>>";
            }
            try (Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) {<commit_msg>Fix windows packaging tests (#47554)<commit_after>/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch licenses this file to you under
 * the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.elasticsearch.packaging.util;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.SuppressForbidden;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Wrapper to run shell commands and collect their outputs in a less verbose way
 */
public class Shell {

    public static final int TAIL_WHEN_TOO_MUCH_OUTPUT = 1000;
    protected final Logger logger =  LogManager.getLogger(getClass());

    final Map<String, String> env = new HashMap<>();
    Path workingDirectory;

    public Shell() {
        this.workingDirectory = null;
    }

    public Map<String, String> getEnv() {
        return env;
    }

    public void setWorkingDirectory(Path workingDirectory) {
        this.workingDirectory = workingDirectory;
    }

    /**
     * Run the provided string as a shell script. On Linux the {@code bash -c [script]} syntax will be used, and on Windows
     * the {@code powershell.exe -Command [script]} syntax will be used. Throws an exception if the exit code of the script is nonzero
     */
    public Result run(String script) {
        return runScript(getScriptCommand(script));
    }

    /**
     * Same as {@link #run(String)}, but does not throw an exception if the exit code of the script is nonzero
     */
    public Result runIgnoreExitCode(String script) {
        return runScriptIgnoreExitCode(getScriptCommand(script));
    }

    public void chown(Path path) throws Exception {
        Platforms.onLinux(() -> run("chown -R elasticsearch:elasticsearch " + path));
        Platforms.onWindows(() -> run(
            "$account = New-Object System.Security.Principal.NTAccount '" + System.getenv("username")  + "'; " +
                "$tempConf = Get-ChildItem '" + path + "' -Recurse; " +
                "$tempConf += Get-Item '" + path + "'; " +
                "$tempConf | ForEach-Object { " +
                "$acl = Get-Acl $_.FullName; " +
                "$acl.SetOwner($account); " +
                "Set-Acl $_.FullName $acl " +
                "}"
        ));
    }

    public Result run( String command, Object... args) {
        String formattedCommand = String.format(Locale.ROOT, command, args);
        return run(formattedCommand);
    }
    private String[] getScriptCommand(String script) {
        if (Platforms.WINDOWS) {
            return powershellCommand(script);
        } else {
            return bashCommand(script);
        }
    }

    private static String[] bashCommand(String script) {
        return Stream.concat(Stream.of("bash", "-c"), Stream.of(script)).toArray(String[]::new);
    }

    private static String[] powershellCommand(String script) {
        return Stream.concat(Stream.of("powershell.exe", "-Command"), Stream.of(script)).toArray(String[]::new);
    }

    private Result runScript(String[] command) {
        Result result = runScriptIgnoreExitCode(command);
        if (result.isSuccess() == false) {
            throw new RuntimeException("Command was not successful: [" + String.join(" ", command) + "]\n   result: " + result.toString());
        }
        return result;
    }

    private Result runScriptIgnoreExitCode(String[] command) {
        ProcessBuilder builder = new ProcessBuilder();
        builder.command(command);
        if (workingDirectory != null) {
            setWorkingDirectory(builder, workingDirectory);
        }
        for (Map.Entry<String, String> entry : env.entrySet()) {
            builder.environment().put(entry.getKey(), entry.getValue());
        }
        final Path stdOut;
        final Path stdErr;
        try {
            Path tmpDir = Paths.get(System.getProperty("java.io.tmpdir"));
            Files.createDirectories(tmpDir);
            stdOut = Files.createTempFile(tmpDir, getClass().getName(), ".out");
            stdErr = Files.createTempFile(tmpDir, getClass().getName(), ".err");
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }

        redirectOutAndErr(builder, stdOut, stdErr);

        try {
            Process process = builder.start();
            if (process.waitFor(10, TimeUnit.MINUTES) == false) {
                if (process.isAlive()) {
                    process.destroyForcibly();
                }
                Result result = new Result(
                    -1,
                    readFileIfExists(stdOut),
                    readFileIfExists(stdErr)
                );
                throw new IllegalStateException(
                    "Timed out running shell command: " + command + "\n" +
                    "Result:\n" + result
                );
            }

            Result result = new Result(
                process.exitValue(),
                readFileIfExists(stdOut),
                readFileIfExists(stdErr)
            );
            logger.info("Ran: {} {}", Arrays.toString(command), result);
            return result;

        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        } finally {
            try {
                FileUtils.deleteIfExists(stdOut);
                FileUtils.deleteIfExists(stdErr);
            } catch (UncheckedIOException e) {
                logger.info("Cleanup of output files failed", e);
            }
        }
    }

    private String readFileIfExists(Path path) throws IOException {
        if (Files.exists(path)) {
            long size = Files.size(path);
            if (size > 100 * 1024) {
                return "<<Too large to read: " + size  + " bytes>>";
            }
            try (Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) {
{'fraction_non_alphanumeric': 0.10935000727061218, 'fraction_numerical': 0.0034171877272066308, 'mean_word_length': 2.327285921625544, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '416956', 'n_tokens_mistral': 3658, 'n_tokens_neox': 3543, 'n_words': 1016}