File: main.go

package info (click to toggle)
gitlab-shell 14.35.0%2Bds1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 23,652 kB
  • sloc: ruby: 1,129; makefile: 583; sql: 391; sh: 384
file content (127 lines) | stat: -rw-r--r-- 3,732 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// The `replace-buildid` tool is used to replace a build ID in an ELF binary with a new build ID.
// Note that this tool is extremely naive: it simply takes the old input ID as string, verifies
// that this ID is contained in the binary exactly once, and then replaces it. It has no knowledge
// about ELF binaries whatsoever.
//
// This tool is mainly used to replace our static GNU build ID we set in our Makefile with a
// derived build ID without having to build binaries twice.

package main

import (
	"bytes"
	"encoding/hex"
	"flag"
	"fmt"
	"io"
	"os"
	"path/filepath"

	"gitlab.com/gitlab-org/gitaly/v16/internal/helper/perm"
)

func main() {
	var inputPath, outputPath, inputBuildID, outputBuildID string

	flag.StringVar(&inputPath, "input", "", "path to the binary whose GNU build ID should be replaced")
	flag.StringVar(&outputPath, "output", "", "path whether the resulting binary should be placed")
	flag.StringVar(&inputBuildID, "input-build-id", "", "static build ID to replace")
	flag.StringVar(&outputBuildID, "output-build-id", "", "new build ID to replace old value with")
	flag.Parse()

	if err := replaceBuildID(inputPath, outputPath, inputBuildID, outputBuildID); err != nil {
		fmt.Fprintf(os.Stderr, "%s\n", err)
		os.Exit(1)
	}
}

func replaceBuildID(inputPath, outputPath, inputBuildID, outputBuildID string) error {
	if inputPath == "" {
		return fmt.Errorf("missing input path")
	}
	if outputPath == "" {
		return fmt.Errorf("missing output path")
	}
	if inputBuildID == "" {
		return fmt.Errorf("missing output path")
	}
	if outputBuildID == "" {
		return fmt.Errorf("missing output path")
	}
	if flag.NArg() > 0 {
		return fmt.Errorf("extra arguments")
	}

	inputBuildIDDecoded, err := hex.DecodeString(inputBuildID)
	if err != nil {
		return fmt.Errorf("decoding input build ID: %w", err)
	}

	outputBuildIDDecoded, err := hex.DecodeString(outputBuildID)
	if err != nil {
		return fmt.Errorf("decoding output build ID: %w", err)
	}

	if len(inputBuildIDDecoded) != len(outputBuildIDDecoded) {
		return fmt.Errorf("input and output build IDs do not have the same length")
	}

	data, err := readAndReplace(inputPath, inputBuildIDDecoded, outputBuildIDDecoded)
	if err != nil {
		return fmt.Errorf("could not replace build ID: %w", err)
	}

	if err := writeBinary(outputPath, data); err != nil {
		return fmt.Errorf("writing binary: %w", err)
	}

	return nil
}

func readAndReplace(binaryPath string, inputBuildID, outputBuildID []byte) ([]byte, error) {
	inputFile, err := os.Open(binaryPath)
	if err != nil {
		return nil, fmt.Errorf("opening input file: %w", err)
	}
	defer inputFile.Close()

	data, err := io.ReadAll(inputFile)
	if err != nil {
		return nil, fmt.Errorf("reading input file: %w", err)
	}

	if occurrences := bytes.Count(data, inputBuildID); occurrences != 1 {
		return nil, fmt.Errorf("exactly one match for old build ID expected, got %d", occurrences)
	}

	return bytes.ReplaceAll(data, inputBuildID, outputBuildID), nil
}

func writeBinary(binaryPath string, contents []byte) error {
	f, err := os.CreateTemp(filepath.Dir(binaryPath), filepath.Base(binaryPath))
	if err != nil {
		return fmt.Errorf("could not create binary: %w", err)
	}
	defer func() {
		_ = os.RemoveAll(f.Name())
		f.Close()
	}()

	if err := f.Chmod(perm.SharedExecutable); err != nil {
		return fmt.Errorf("could not change permissions: %w", err)
	}

	if _, err := io.Copy(f, bytes.NewReader(contents)); err != nil {
		return fmt.Errorf("could not write binary: %w", err)
	}

	if err := f.Close(); err != nil {
		return fmt.Errorf("could not close binary: %w", err)
	}

	if err := os.Rename(f.Name(), binaryPath); err != nil {
		return fmt.Errorf("could not move binary into place: %w", err)
	}

	return nil
}