<commit_before>package main

import (
	"archive/tar"
	"bytes"
	"io"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"
	"time"
)

var (
	commonCmdCaptures = []CommandCapturer{
		{"/bin/date", nil},
		{"/bin/uname", []string{"-a"}},
		{"/bin/ps", []string{"uax"}},
		{"/bin/netstat", []string{"-tulpn"}},
		{"/sbin/iptables-save", nil},
		{"/sbin/ifconfig", nil},
		{"/sbin/route", nil},
		{"/usr/sbin/brctl", []string{"show"}},
		{"/bin/dmesg", nil},
		{"/usr/bin/docker", []string{"ps"}},
		{"/usr/bin/tail", []string{"-20000", "/var/log/docker.log"}},
		{"/usr/bin/tail", []string{"-20000", "/var/log/messages"}},
		{"/bin/mount", nil},
		{"/bin/df", []string{"-h"}},
		{"/bin/ls", []string{"-l", "/var"}},
		{"/bin/ls", []string{"-l", "/var/lib"}},
		{"/bin/ls", []string{"-l", "/var/lib/docker"}},
		{"/usr/bin/diagnostics", nil},
		{"/bin/ping", []string{"-w", "5", "8.8.8.8"}},
		{"/bin/cat", []string{"/etc/docker/daemon.json"}},
		{"/bin/cat", []string{"/etc/network/interfaces"}},
		{"/bin/cat", []string{"/etc/resolv.conf"}},
		{"/bin/cat", []string{"/etc/sysctl.conf"}},
		{"/usr/bin/dig", []string{"docker.com"}},
		{"/usr/bin/dig", []string{"@8.8.8.8", "docker.com"}},
		{"/usr/bin/wget", []string{"-O", "-", "http://www.docker.com/"}},
		{"/usr/bin/wget", []string{"-O", "-", "http://192.168.3.11/"}}, // a www.docker.com address
		{"/usr/bin/wget", []string{"-O", "-", "http://216.58.213.68/"}},   // a www.google.com address
		{"/usr/bin/wget", []string{"-O", "-", "http://172.16.58.3/"}},  // a www.wikipedia.com address
	}
	localCmdCaptures = []CommandCapturer{
		{"/usr/bin/tail", []string{"-100", "/var/log/proxy-vsockd.log"}},
		{"/usr/bin/tail", []string{"-100", "/var/log/vsudd.log"}},
	}
	localCaptures = []Capturer{NewDatabaseCapturer()}
)

func init() {
	for _, c := range commonCmdCaptures {
		localCaptures = append(localCaptures, c)
	}
	for _, c := range localCmdCaptures {
		localCaptures = append(localCaptures, c)
	}
}

// Capturer defines behavior for structs which will capture arbitrary
// diagnostic information and write it to a tar archive with a timeout.
type Capturer interface {
	Capture(time.Duration, *tar.Writer)
}

type CommandCapturer struct {
	command string
	args    []string
}

func (cc CommandCapturer) Capture(timeout time.Duration, w *tar.Writer) {
	log.Printf("Running %s", cc.command)
	c := exec.Command(cc.command, cc.args...)
	stdoutPipe, err := c.StdoutPipe()
	if err != nil {
		log.Fatalf("Failed to create stdout pipe: %s", err)
	}
	stderrPipe, err := c.StderrPipe()
	if err != nil {
		log.Fatalf("Failed to create stderr pipe: %s", err)
	}
	var stdoutBuffer bytes.Buffer
	var stderrBuffer bytes.Buffer
	done := make(chan int)
	go func() {
		io.Copy(&stdoutBuffer, stdoutPipe)
		done <- 0
	}()
	go func() {
		io.Copy(&stderrBuffer, stderrPipe)
		done <- 0
	}()
	var timer *time.Timer
	timer = time.AfterFunc(timeout, func() {
		timer.Stop()
		if c.Process != nil {
			c.Process.Kill()
		}
	})
	_ = c.Run()
	<-done
	<-done
	timer.Stop()

	name := strings.Join(append([]string{path.Base(cc.command)}, cc.args...), " ")

	hdr := &tar.Header{
		Name: name + ".stdout",
		Mode: 0644,
		Size: int64(stdoutBuffer.Len()),
	}
	if err = w.WriteHeader(hdr); err != nil {
		log.Fatalln(err)
	}
	if _, err = w.Write(stdoutBuffer.Bytes()); err != nil {
		log.Fatalln(err)
	}
	hdr = &tar.Header{
		Name: name + ".stderr",
		Mode: 0644,
		Size: int64(stderrBuffer.Len()),
	}
	if err = w.WriteHeader(hdr); err != nil {
		log.Fatalln(err)
	}
	if _, err = w.Write(stderrBuffer.Bytes()); err != nil {
		log.Fatalln(err)
	}
}

type DatabaseCapturer struct {
	*CommandCapturer
}

func NewDatabaseCapturer() DatabaseCapturer {
	return DatabaseCapturer{
		&CommandCapturer{
			command: "/bin/cat",
		},
	}
}

func (dc DatabaseCapturer) Capture(timeout time.Duration, w *tar.Writer) {
	// Dump the database<commit_msg>Fix race condition(s) in command collection code<commit_after>package main

import (
	"archive/tar"
	"bytes"
	"context"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"
	"time"
)

var (
	commonCmdCaptures = []CommandCapturer{
		{"/bin/date", nil},
		{"/bin/uname", []string{"-a"}},
		{"/bin/ps", []string{"uax"}},
		{"/bin/netstat", []string{"-tulpn"}},
		{"/sbin/iptables-save", nil},
		{"/sbin/ifconfig", nil},
		{"/sbin/route", nil},
		{"/usr/sbin/brctl", []string{"show"}},
		{"/bin/dmesg", nil},
		{"/usr/bin/docker", []string{"ps"}},
		{"/usr/bin/tail", []string{"-20000", "/var/log/docker.log"}},
		{"/usr/bin/tail", []string{"-20000", "/var/log/messages"}},
		{"/bin/mount", nil},
		{"/bin/df", []string{"-h"}},
		{"/bin/ls", []string{"-l", "/var"}},
		{"/bin/ls", []string{"-l", "/var/lib"}},
		{"/bin/ls", []string{"-l", "/var/lib/docker"}},
		{"/usr/bin/diagnostics", nil},
		{"/bin/ping", []string{"-w", "5", "8.8.8.8"}},
		{"/bin/cat", []string{"/etc/docker/daemon.json"}},
		{"/bin/cat", []string{"/etc/network/interfaces"}},
		{"/bin/cat", []string{"/etc/resolv.conf"}},
		{"/bin/cat", []string{"/etc/sysctl.conf"}},
		{"/usr/bin/dig", []string{"docker.com"}},
		{"/usr/bin/dig", []string{"@8.8.8.8", "docker.com"}},
		{"/usr/bin/wget", []string{"-O", "-", "http://www.docker.com/"}},
		{"/usr/bin/wget", []string{"-O", "-", "http://192.168.3.11/"}}, // a www.docker.com address
		{"/usr/bin/wget", []string{"-O", "-", "http://216.58.213.68/"}},   // a www.google.com address
		{"/usr/bin/wget", []string{"-O", "-", "http://172.16.58.3/"}},  // a www.wikipedia.com address
	}
	localCmdCaptures = []CommandCapturer{
		{"/usr/bin/tail", []string{"-100", "/var/log/proxy-vsockd.log"}},
		{"/usr/bin/tail", []string{"-100", "/var/log/vsudd.log"}},
	}
	localCaptures = []Capturer{NewDatabaseCapturer()}
)

func init() {
	for _, c := range commonCmdCaptures {
		localCaptures = append(localCaptures, c)
	}
	for _, c := range localCmdCaptures {
		localCaptures = append(localCaptures, c)
	}
}

// Capturer defines behavior for structs which will capture arbitrary
// diagnostic information and write it to a tar archive with a timeout.
type Capturer interface {
	Capture(time.Duration, *tar.Writer)
}

type CommandCapturer struct {
	command string
	args    []string
}

func (cc CommandCapturer) Capture(timeout time.Duration, w *tar.Writer) {
	stdout := &bytes.Buffer{}
	stderr := &bytes.Buffer{}
	done := make(chan struct{})

	name := strings.Join(append([]string{path.Base(cc.command)}, cc.args...), " ")
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	cmd := exec.CommandContext(ctx, cc.command, cc.args...)
	cmd.Stdout = stdout
	cmd.Stderr = stderr

	go runCmd(cmd, done)

	select {
	case <-ctx.Done():
		log.Println("ERROR:", ctx.Err())
	case <-done:
		tarWrite(w, stdout, name+".stdout")
		tarWrite(w, stderr, name+".stderr")
	}

	cancel()
}

// TODO(nathanleclaire): Is the user of log.Fatalln in this function really the
// right choice?  i.e., should the program really exit on failure here?
func tarWrite(w *tar.Writer, buf *bytes.Buffer, headerName string) {
	contents, err := ioutil.ReadAll(buf)
	if err != nil {
		log.Fatalln(err)
	}

	log.Println("HEADER:", headerName)
	log.Println("{")
	contentLines := strings.Split(string(contents), "\n")
	for _, line := range contentLines {
		log.Println(line)
	}
	log.Println("}")

	hdr := &tar.Header{
		Name: headerName,
		Mode: 0644,
		Size: int64(len(contents)),
	}
	if err := w.WriteHeader(hdr); err != nil {
		log.Fatalln(err)
	}
	if _, err := w.Write(contents); err != nil {
		log.Fatalln(err)
	}
}

func runCmd(cmd *exec.Cmd, done chan<- struct{}) {
	if err := cmd.Run(); err != nil {
		log.Println("ERROR:", err)
	}
	done <- struct{}{}
}

type DatabaseCapturer struct {
	*CommandCapturer
}

func NewDatabaseCapturer() DatabaseCapturer {
	return DatabaseCapturer{
		&CommandCapturer{
			command: "/bin/cat",
		},
	}
}

func (dc DatabaseCapturer) Capture(timeout time.Duration, w *tar.Writer) {
	// Dump the database
{'fraction_non_alphanumeric': 0.2385297845373891, 'fraction_numerical': 0.015716096324461342, 'mean_word_length': 3.7940461725394896, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 8, 'xml': 0}, 'pii_count': 10, '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': '4996286', 'n_tokens_mistral': 3287, 'n_tokens_neox': 3090, 'n_words': 607}