aboutsummaryrefslogtreecommitdiffstats
path: root/brp-mangle-shebangs
blob: ef85ee43abccc285071c726e09dca05045816834 (plain)
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#!/bin/bash -eu

# If using normal root, avoid changing anything.
if [ -z "$RPM_BUILD_ROOT" -o "$RPM_BUILD_ROOT" = "/" ]; then
  exit 0
fi

exclude_files=""
exclude_files_from=""
exclude_shebangs=""
exclude_shebangs_from=""

usage() {
  local verbose=$1 && shift
  local outfile=$1 && shift
  local status=$1 && shift

  (
    echo 'usage: brp-mangle-shebangs [--files <regexp>] [--files-from <file>] [--shebangs <regexp>] [--shebangs-from <file>]'
    if [ "${verbose}" == "yes" ]; then
      echo '  --files: extended regexp of files to ignore'
      echo '  --files-from: file containing a list of extended regexps of files to ignore'
      echo '  --shebangs: extended regexp of shebangs to ignore'
      echo '  --shebangs-from: file containing a list of extended regexps of shebangs to ignore'
    fi
  ) >>${outfile}
  exit ${status}
}

while [ $# -gt 0 ] ; do
  case "$1" in
    --files)
      exclude_files="${2}"
      shift
      ;;
    --files=*)
      exclude_files="${1##--files=}"
      ;;
    --files-from)
      exclude_files_from="${2}"
      shift
      ;;
    --files-from=*)
      exclude_files_from="${1##--files-from=}"
      ;;
    --shebangs)
      exclude_shebangs="${2}"
      shift
      ;;
    --shebangs=*)
      exclude_shebangs="${1##--shebangs=}"
      ;;
    --shebangs-from)
      exclude_shebangs_from="${2}"
      shift
      ;;
    --shebangs-from=*)
      exclude_shebangs_from="${1##--shebangs-from=}"
      ;;
    --help|--usage|"-?"|-h)
      usage yes /dev/stdout 0
      ;;
    *)
      echo "Unknown option \"${1}\"" 1>&2
      usage no /dev/stderr 1
      ;;
  esac
  shift
done

cd "$RPM_BUILD_ROOT"

# Large packages such as kernel can have thousands of executable files.
# We take care to not fork/exec thousands of "file"s and "grep"s,
# but run just two of them.
# (Take care to exclude filenames which would mangle "file" output).
find -executable -type f ! -path '*:*' ! -path $'*\n*' \
| file -N --mime-type -f - \
| grep -P ".+(?=: text/)" \
| {
fail=0
while IFS= read -r line; do
  f=${line%%:*}

  # Remove the dot
  path="${f#.}"

  if [ -n "$exclude_files" ]; then
    echo "$path" | grep -q -E "$exclude_files" && continue
  fi
  if [ -n "$exclude_files_from" ]; then
    echo "$path" | grep -q -E -f "$exclude_files_from" && continue
  fi


  if ! read shebang_line < "$f"; then
    echo >&2 "*** WARNING: Cannot read the first line from $f, removing executable bit"
    ts=$(stat -c %y "$f")
    chmod -x "$f"
    touch -d "$ts" "$f"
    continue
  fi

  orig_shebang="${shebang_line#\#!}"
  if [ "$orig_shebang" = "$shebang_line" ]; then
    echo >&2 "*** WARNING: $f is executable but has no shebang, removing executable bit"
    ts=$(stat -c %y "$f")
    chmod -x "$f"
    touch -d "$ts" "$f"
    continue
  fi

  # Trim spaces
  while shebang="${orig_shebang//  / }"; [ "$shebang" != "$orig_shebang" ]; do
    orig_shebang="$shebang"
  done
  # Treat "#! /path/to " as "#!/path/to"
  orig_shebang="${orig_shebang# }"

  shebang="$orig_shebang"

  if [ -z "$shebang" ]; then
    echo >&2 "*** WARNING: $f is executable but has empty shebang, removing executable bit"
    ts=$(stat -c %y "$f")
    chmod -x "$f"
    touch -d "$ts" "$f"
    continue
  fi
  if [ -n "${shebang##/*}" ]; then
    echo >&2 "*** ERROR: $f has shebang which doesn't start with '/' ($shebang)"
    fail=1
    continue
  fi

  if ! { echo "$shebang" | grep -q -P "^/(?:usr/)?(?:bin|sbin)/"; }; then
    continue
  fi

  # Replace "special" env shebang:
  # /whatsoever/env /whatever/foo → /whatever/foo
  shebang=$(echo "$shebang" | sed -r -e 's@^(.+)/env /(.+)$@/\2@')
  # /whatsoever/env foo → /whatsoever/foo
  shebang=$(echo "$shebang" | sed -r -e 's@^(.+/)env (.+)$@\1\2@')

  # If the shebang now starts with /bin, change it to /usr/bin
  # https://bugzilla.redhat.com/show_bug.cgi?id=1581757
  shebang=$(echo "$shebang" | sed -r -e 's@^/bin/@/usr/bin/@')

  # Replace ambiguous python with python2
  py_shebang=$(echo "$shebang" | sed -r -e 's@/usr/bin/python(\s|$)@/usr/bin/python2\1@')

  if [ "$shebang" != "$py_shebang" ]; then
    echo >&2 "*** ERROR: ambiguous python shebang in $path: #!$orig_shebang. Change it to python3 (or python2) explicitly."
    fail=1
  elif [ "#!$shebang" != "#!$orig_shebang" ]; then
    echo "mangling shebang in $path from $orig_shebang to #!$shebang"
    ts=$(stat -c %y "$f")
    sed -i -e "1c #!$shebang" "$f"
    touch -d "$ts" "$f"
  fi

done

exit $fail
}