aboutsummaryrefslogtreecommitdiff
path: root/brep/handler/upload/upload-bindist-clean.in
blob: 99914a74577ed591f50c001d66af24a9dcd82c50 (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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
#!/usr/bin/env bash

# file      : brep/handler/upload/upload-bindist-clean.in
# license   : MIT; see accompanying LICENSE file

# Remove expired package configuration directories created by the
# upload-bindist handler.
#
# Specifically, perform the following steps:
#
# - Recursively scan the specified root directory and collect the package
#   configuration directories with age older than the specified timeout (in
#   minutes). Recognize the package configuration directories by matching the
#   *-????-??-??T??:??:??Z* pattern and calculate their age based on the
#   modification time of the packages.sha256 file they may contain. If
#   packages.sha256 doesn't exist in the configuration directory, then
#   consider it as still being prepared and skip.
#
# - Iterate over the expired package configuration directories and for each of
#   them:
#
#   - Lock the root directory.
#
#   - Re-check the expiration criteria.
#
#   - Remove the package configuration symlink if it refers to this directory.
#
#   - Remove this directory.
#
#   - Remove all the the parent directories of this directory which become
#     empty, up to (but excluding) the root directory.
#
#   - Unlock the root directory.
#
usage="usage: $0 <root> <timeout>"

# Diagnostics.
#
verbose= #true

# The root directory lock timeout (in seconds).
#
lock_timeout=60

trap "{ exit 1; }" ERR
set -o errtrace     # Trap in functions and subshells.
set -o pipefail     # Fail if any pipeline command fails.
shopt -s lastpipe   # Execute last pipeline command in the current shell.
shopt -s nullglob   # Expand no-match globs to nothing rather than themselves.

function info () { echo "$*" 1>&2; }
function error () { info "$*"; exit 1; }
function trace () { if [ "$verbose" ]; then info "$*"; fi }

# Trace a command line, quoting empty arguments as well as those that contain
# spaces.
#
function trace_cmd () # <cmd> <arg>...
{
  if [[ "$verbose" ]]; then
    local s="+"
    while [ $# -gt 0 ]; do
      if [ -z "$1" -o -z "${1##* *}" ]; then
        s="$s '$1'"
      else
        s="$s $1"
      fi

      shift
    done

    info "$s"
  fi
}

# Trace and run a command.
#
function run () # <cmd> <arg>...
{
  trace_cmd "$@"
  "$@"
}

if [[ "$#" -ne 2 ]]; then
  error "$usage"
fi

# Package configurations root directory.
#
root_dir="${1%/}"
shift

if [[ -z "$root_dir" ]]; then
  error "$usage"
fi

if [[ ! -d "$root_dir" ]]; then
  error "'$root_dir' does not exist or is not a directory"
fi

# Package configuration directories timeout.
#
timeout="$1"
shift

if [[ ! "$timeout" =~ ^[0-9]+$ ]]; then
  error "$usage"
fi

# Note that while the '%s' date format is not POSIX, it is supported on both
# Linux and FreeBSD.
#
expiration=$(($(date -u +"%s") - $timeout * 60))

# Collect the list of expired package configuration directories.
#
expired_dirs=()

run find "$root_dir" -type d -name "*-????-??-??T??:??:??Z*" | while read d; do
  f="$d/packages.sha256"

  # Note that while the -r date option is not POSIX, it is supported on both
  # Linux and FreeBSD.
  #
  trace_cmd date -u -r "$f" +"%s"
  if t="$(date -u -r "$f" +"%s" 2>/dev/null)" && (($t <= $expiration)); then
    expired_dirs+=("$d")
  fi
done

if [[ "${#expired_dirs[@]}" -eq 0 ]]; then
  exit 0 # Nothing to do.
fi

# Make sure the root directory lock file exists.
#
lock="$root_dir/upload.lock"
run touch "$lock"

# Remove the expired package configuration directories, symlinks which refer
# to them, and the parent directories which become empty.
#
for d in "${expired_dirs[@]}"; do
  # Deduce the path of the potential package configuration symlink that may
  # refer to this package configuration directory by stripping the
  # -<timestamp>[-<number>] suffix.
  #
  l="$(sed -n -re 's/^(.+)-[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z(-[0-9]+)?$/\1/p' <<<"$d")"
  if [[ -z "$l" ]]; then
    error "invalid name '$d' for package configuration directory"
  fi

  f="$d/packages.sha256"

  # Open the reading file descriptor and lock the root directory. Fail if
  # unable to lock before timeout.
  #
  trace "+ exec {lfd}<$lock"
  exec {lfd}<"$lock"

  if ! run flock -w "$lock_timeout" "$lfd"; then
    error "unable to lock root directory"
  fi

  # Now, as the lock is acquired, recheck the package configuration directory
  # expiration criteria (see above) and, if it still holds, remove this
  # directory, the package configuration symlink if it refers to it, and all
  # the parent directories which become empty up to (but excluding) the root
  # directory.
  #
  trace_cmd date -u -r "$f" +"%s"
  if t="$(date -u -r "$f" +"%s" 2>/dev/null)" && (($t <= $expiration)); then
    # Remove the package configuration symlink.
    #
    # Do this first to avoid dangling symlinks which may potentially be
    # exposed by brep.
    #
    # Note that while the realpath utility is not POSIX, it is present on
    # both Linux and FreeBSD.
    #
    if [[ -L "$l" ]]; then
      p="$(realpath "$l")"
      if [[ "$p" == "$d" ]]; then
        run rm "$l"
      fi
    fi

    # Remove the package configuration directory.
    #
    # Note that this directory contains files copied from a subdirectory of
    # upload-data. These files are normally owned by the Apache2 user/group
    # and have rw-r--r-- permissions. This script is normally executed as the
    # brep user/group and thus the uploads root directory and all its
    # subdirectories must have read, write, and execute permissions granted to
    # the brep user, for example, by using ACL (see INSTALL file for
    # details). Since cp preserves the file permissions by default, these
    # files effective permissions will normally be r-- (read-only) for this
    # script. In this case rm pops up the 'remove write-protected regular
    # file' prompt by default prior to removing these files. To suppress the
    # prompt we will pass the -f option to rm.
    #
    run rm -rf "$d"

    # Remove the empty parent directories.
    #
    # Note that we iterate until the rmdir command fails, presumably because a
    # directory is not empty.
    #
    d="$(dirname "$d")"
    while [[ "$d" != "$root_dir" ]]; do
      trace_cmd rmdir "$d"
      if rmdir "$d" 2>/dev/null; then
        d="$(dirname "$d")"
      else
        break
      fi
    done
  fi

  # Close the file descriptor and unlock the root directory.
  #
  trace "+ exec {lfd}<&-"
  exec {lfd}<&-
done