Repository navigation
Expand file tree
/
Copy pathfetch-src.ksh
More file actions
executable file
·581 lines (539 loc) · 15.5 KB
/
Copy pathfetch-src.ksh
File metadata and controls
executable file
·581 lines (539 loc) · 15.5 KB
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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
#!/bin/ksh
# Copyright (c) 2025-2026 David Uhden Collado <david@uhden.dev>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
log() {
print "$(date '+%Y-%m-%d %H:%M:%S')" \
"[INFO] $*" >&2
}
warn() {
print "$(date '+%Y-%m-%d %H:%M:%S')" \
"[WARN] $*" >&2
}
error() {
print "$(date '+%Y-%m-%d %H:%M:%S')" \
"[ERROR] $*" >&2
}
# Verify that the script is run as root
check_root() {
if [ "$(id -u)" -ne 0 ]; then
error "This script must be run as root."
exit 1
fi
}
# Function to remove the src content
remove_src_content() {
rm -rf /usr/src/*
}
# Function to remove the xenocara content
remove_xenocara_content() {
rm -rf /usr/xenocara/*
}
# Set CVSROOT in .profile if not already configured
set_cvsroot() {
if [ ! -f "$HOME/.profile" ] ||
! grep -Fq "export CVSROOT=anoncvs@anoncvs.eu.openbsd.org:/cvs" \
"$HOME/.profile"; then
print "export CVSROOT=anoncvs@anoncvs.eu.openbsd.org:/cvs" \
>>"$HOME/.profile"
log "CVSROOT variable added to ~/.profile"
else
log "CVSROOT variable already exists in ~/.profile"
fi
export CVSROOT="anoncvs@anoncvs.eu.openbsd.org:/cvs"
}
# Function to select which repository to checkout (src or xenocara)
select_repository_to_checkout() {
log "Select the repository to clone (checkout):"
select SELECTED_REPO in src xenocara; do
if [ -n "$SELECTED_REPO" ]; then
log "You selected $SELECTED_REPO"
break
else
warn "Invalid selection. Please try again."
fi
done
}
# Function to checkout the selected tree using CVS
checkout_selected_repository() {
cd /usr || exit 1
case "$SELECTED_REPO" in
src)
remove_src_content
log "Checking out src from anoncvs..."
cvs -qd anoncvs@anoncvs.eu.openbsd.org:/cvs checkout -P src
;;
xenocara)
remove_xenocara_content
log "Checking out xenocara from anoncvs..."
cvs -qd anoncvs@anoncvs.eu.openbsd.org:/cvs checkout -P xenocara
;;
*)
error "Unknown repository: $SELECTED_REPO"
exit 1
;;
esac
}
# Function to ask if user wants to copy from wip-openbsd-src (optional)
ask_copy_from_wip() {
log "Copy a directory from 'wip-openbsd-src'" \
"into the checked-out tree?"
select ANSWER in \
"One directory" "Selected directories" \
"All directories" "No"; do
case "$ANSWER" in
"One directory")
DO_COPY=1
COPY_ALL=0
COPY_LIST=0
break
;;
"Selected directories")
DO_COPY=1
COPY_ALL=0
COPY_LIST=1
break
;;
"All directories")
DO_COPY=1
COPY_ALL=1
COPY_LIST=0
break
;;
No)
DO_COPY=0
break
;;
*) warn "Invalid selection. Please try again." ;;
esac
done
}
# Resolve local wip-openbsd-src path. Checks, in order:
# 1. WIP_OPENBSD_SRC_DIR environment variable
# 2. Script's own directory (if named wip-openbsd-src)
# 3. Common locations under $HOME and /usr
# 4. Current working directory or its parent
# 5. Limited filesystem search
resolve_local_src_dir() {
typeset dir
# 1. Explicit environment variable
if [ -n "${WIP_OPENBSD_SRC_DIR:-}" ] &&
[ -d "$WIP_OPENBSD_SRC_DIR/.git" ]; then
log "Using WIP_OPENBSD_SRC_DIR=$WIP_OPENBSD_SRC_DIR"
print "$WIP_OPENBSD_SRC_DIR"
return 0
fi
# 2. Script directory (walk up to find the repo)
dir=$(
unset CDPATH
cd -- "$(dirname -- "$0")" 2>/dev/null && pwd -P
)
while [ -n "$dir" ] && [ "$dir" != "/" ]; do
if [ "$(basename "$dir")" = "wip-openbsd-src" ] &&
[ -d "$dir/.git" ]; then
log "Found wip-openbsd-src at $dir"
print "$dir"
return 0
fi
dir=$(dirname "$dir")
done
# 3. Common locations
for dir in \
"${HOME:-}/wip-openbsd-src" \
/root/wip-openbsd-src \
/usr/wip-openbsd-src \
"${HOME:-}/git/wip-openbsd-src"; do
if [ -d "$dir/.git" ]; then
log "Found wip-openbsd-src at $dir"
print "$dir"
return 0
fi
done
# 4. Scan home directories
for dir in /home/*/wip-openbsd-src /home/*/git/wip-openbsd-src; do
if [ -d "$dir/.git" ]; then
log "Found wip-openbsd-src at $dir"
print "$dir"
return 0
fi
done
# 5. Current directory or parent
if [ -d "$PWD/.git" ] &&
[ "$(basename "$PWD")" = "wip-openbsd-src" ]; then
print "$PWD"
return 0
fi
if [ -d "$PWD/wip-openbsd-src/.git" ]; then
print "$PWD/wip-openbsd-src"
return 0
fi
# 6. Limited search under /home, /usr, /root
warn "Searching /home and /usr for wip-openbsd-src..."
dir=$(find /home /usr /root -maxdepth 5 \
-type d -name "wip-openbsd-src" \
-exec test -d '{}/.git' ';' \
-print -quit 2>/dev/null)
if [ -n "$dir" ]; then
log "Found wip-openbsd-src at $dir"
print "$dir"
return 0
fi
error "wip-openbsd-src directory not found." \
"Set WIP_OPENBSD_SRC_DIR to its location."
exit 1
}
# Change to the wip-openbsd-src directory.
move_to_wip_openbsd_src() {
wip_openbsd_src_dir=$(resolve_local_src_dir)
cd "$wip_openbsd_src_dir" || {
error "Could not cd to $wip_openbsd_src_dir"
exit 1
}
}
# Function to list directories in the current directory and select one
list_directories() {
typeset directories
directories=$(list_all_directories) || return 1
log "Select a directory to copy from wip-openbsd-src:"
select DIRECTORY in $directories; do
if [ -n "$DIRECTORY" ]; then
log "You selected $DIRECTORY"
DIRECTORY=${DIRECTORY%/} # Remove the trailing slash
break
else
warn "Invalid selection. Please try again."
fi
done
}
# List known components by their canonical upstream destinations.
# Keep the standalone source/build layout in this repository unchanged.
list_all_directories() {
typeset component
for component in src/bin/pax xenocara/app/fvwm; do
resolve_component "$component" || return 1
if [ -z "${TARGET_KIND:-}" ] ||
[ "$TARGET_KIND" = "$COMPONENT_TREE" ]; then
print -r -- "$component"
fi
done
}
# Function to prompt for a space-separated list of directories.
prompt_selected_directories() {
log "Enter component paths separated by spaces (src/bin/pax or xenocara/app/fvwm):"
print -n "> "
read -r SELECTED_DIRECTORIES
[ -n "${SELECTED_DIRECTORIES:-}" ] || {
error "No directories entered."
exit 1
}
}
# Function to choose between /usr/src or /usr/xenocara as target tree
# Offer trees that exist (the cloned one, or both if present)
choose_target_tree() {
options=""
[ -d /usr/src ] && options="$options /usr/src"
[ -d /usr/xenocara ] && options="$options /usr/xenocara"
if [ -z "$options" ]; then
error "No destination trees available under /usr."
exit 1
fi
log "Select the target tree for the copy:"
select TARGET_TREE in $options; do
if [ -n "$TARGET_TREE" ]; then
log "You selected $TARGET_TREE"
break
else
warn "Invalid selection. Please try again."
fi
done
}
# Exact mapping avoids placing pax at the src root, or FVWM in src.
resolve_component() {
case "$1" in
pax | src/bin/pax)
COMPONENT_TREE=src
COMPONENT_PATH=bin/pax
COMPONENT_DIR="pax"
;;
fvwm | xenocara/app/fvwm)
COMPONENT_TREE=xenocara
COMPONENT_PATH=app/fvwm
COMPONENT_DIR=fvwm
;;
*)
error "Unknown component: $1"
return 1
;;
esac
if [ -L "$COMPONENT_DIR" ] || [ -L "$COMPONENT_DIR/Makefile" ] ||
[ ! -f "$COMPONENT_DIR/Makefile" ]; then
error "Missing or symlinked component: $COMPONENT_DIR"
return 1
fi
}
detect_target_tree() {
[ -f "$TARGET_TREE/Makefile" ] || {
error "Not a source checkout: $TARGET_TREE"
return 1
}
if [ -d "$TARGET_TREE/bin" ] && [ -d "$TARGET_TREE/sys" ]; then
TARGET_KIND=src
elif [ -d "$TARGET_TREE/app" ] && [ -d "$TARGET_TREE/lib" ]; then
TARGET_KIND=xenocara
else
error "Cannot identify src or xenocara tree: $TARGET_TREE"
return 1
fi
}
validate_selection() {
resolve_component "$1" || return 1
if [ "$COMPONENT_TREE" != "$TARGET_KIND" ]; then
error "$1 belongs in $COMPONENT_TREE, not $TARGET_KIND"
return 1
fi
}
# Flatten a path into a single filename component. Only pdksh constructs
# are used: ${var//pat/rep} is ksh93-only and the ksh shipped with the
# OpenBSD base system (pdksh) rejects it with "bad substitution".
flatten_path() {
print -r -- "$1" | tr '/' '_'
}
# Emit the tracked files under a component, relative to its root and sorted,
# excluding the CVS metadata directories. Used to build the created/deleted
# lists that drive 'cvs add' and 'cvs remove'.
list_component_files() {
(
cd "$1" || exit 1
find . -type d -name CVS -prune -o -type f -print
) | sed -e 's|^\./||' -e '/^$/d' | LC_ALL=C sort
}
# Like list_component_files(), but directories only. CVS has to register a
# new directory before the files it contains, so the created-directory list is
# kept apart from the created-file list.
list_component_dirs() {
(
cd "$1" || exit 1
find . -type d -name CVS -prune -o -type d -print
) | sed -e 's|^\./||' -e '/^$/d' | LC_ALL=C sort
}
# Record the files that disappeared from and appeared in a replaced
# component. The two plain-text lists live in $TARGET_TREE/.wip-changes/ and
# contain one path per line, relative to the component root: the created list
# feeds 'cvs add', the deleted list feeds 'cvs remove'. This is what makes it
# possible to build a correct 'cvs diff' after a wholesale directory
# replacement, since the new files come from a Git checkout and have no CVS
# metadata of their own.
write_change_lists() {
typeset old=$1 new=$2 label=$3
typeset dir="$TARGET_TREE/.wip-changes"
typeset tmp
if [ -L "$dir" ]; then
warn "Refusing symlinked changes directory: $dir"
return 1
fi
mkdir -p "$dir" || return 1
tmp=$(mktemp -d "$dir/.$label.XXXXXXXX") || return 1
if ! list_component_files "$old" >"$tmp/oldfiles" ||
! list_component_files "$new" >"$tmp/newfiles" ||
! list_component_dirs "$old" >"$tmp/olddirs" ||
! list_component_dirs "$new" >"$tmp/newdirs" ||
! LC_ALL=C comm -13 "$tmp/oldfiles" "$tmp/newfiles" \
>"$dir/$label.created.txt" ||
! LC_ALL=C comm -23 "$tmp/oldfiles" "$tmp/newfiles" \
>"$dir/$label.deleted.txt" ||
! LC_ALL=C comm -13 "$tmp/olddirs" "$tmp/newdirs" \
>"$dir/$label.created-dirs.txt"; then
rm -rf "$tmp"
return 1
fi
rm -rf "$tmp"
log "Change lists: $dir/$label.created.txt" \
"$dir/$label.deleted.txt" \
"$dir/$label.created-dirs.txt"
}
# Replace a single component, keeping a recoverable backup and all nested
# CVS metadata. Sibling components and parent Makefiles are never removed.
copy_directory() {
typeset target parent stage backup="" cvs
validate_selection "$DIRECTORY" || return 1
parent=${COMPONENT_PATH%/*}
target="$TARGET_TREE/$COMPONENT_PATH"
if [ ! -d "$TARGET_TREE/$parent" ] ||
[ -L "$TARGET_TREE/$parent" ] || [ -L "$target" ] ||
{ [ -e "$target" ] && [ ! -d "$target" ]; }; then
error "Invalid component destination: $target"
return 1
fi
stage=$(mktemp -d "$TARGET_TREE/.wip-src.XXXXXXXX") || {
error "Cannot create staging directory under $TARGET_TREE"
return 1
}
cp -Rp "$COMPONENT_DIR" "$stage/component" || return 1
find "$stage/component" -type d -exec chmod a+rx,go-w {} + || return 1
find "$stage/component" -type f -exec chmod a+r,go-w {} + || return 1
find "$stage/component" -type f -perm -0100 -exec chmod a+x {} + || return 1
if [ -d "$target" ]; then
(cd "$target" && find . -type d -name CVS -prune) |
while IFS= read -r cvs; do
mkdir -p "$stage/component/${cvs%/*}" || exit 1
cp -Rp "$target/$cvs" "$stage/component/$cvs" || exit 1
done || return 1
write_change_lists "$target" "$stage/component" \
"$COMPONENT_DIR" || return 1
if [ -L "$TARGET_TREE/.wip-backups" ]; then
error "Refusing symlinked backup directory"
return 1
fi
mkdir -p "$TARGET_TREE/.wip-backups" || return 1
backup=$(mktemp -d \
"$TARGET_TREE/.wip-backups/$COMPONENT_DIR.XXXXXXXX") || {
error "Cannot create backup directory" \
"under $TARGET_TREE/.wip-backups"
return 1
}
mv "$target" "$backup/component" || return 1
log "Previous $COMPONENT_PATH saved in $backup/component"
fi
if ! mv "$stage/component" "$target"; then
[ -z "$backup" ] || mv "$backup/component" "$target"
error "Copy failed; staging directory: $stage"
return 1
fi
rmdir "$stage" || return 1
log "$COMPONENT_DIR copied to $target"
}
copy_all_directories() {
typeset directories
directories=$(list_all_directories) || return 1
for DIRECTORY in $directories; do
copy_directory || return 1
done
}
copy_selected_directories() {
# Validate the complete selection before changing any destination.
for DIRECTORY in $SELECTED_DIRECTORIES; do
validate_selection "$DIRECTORY" || return 1
done
for DIRECTORY in $SELECTED_DIRECTORIES; do
copy_directory || return 1
done
}
# Function to create the user 'user' with a random password
create_user_with_random_password() {
USER_TO_CREATE="user"
# Generate a random password
PASSWORD=$(openssl rand -base64 12)
# Create the user with a home directory and set the shell to /bin/ksh
useradd -m -s /bin/ksh "$USER_TO_CREATE"
# Encrypt the password and set it using usermod
ENCRYPTED_PASSWORD=$(openssl passwd -1 "$PASSWORD")
usermod -p "$ENCRYPTED_PASSWORD" "$USER_TO_CREATE"
log "User 'user' created with password: $PASSWORD"
}
# Function to configure doas
configure_doas() {
cp /etc/examples/doas.conf /etc/doas.conf
print "permit keepenv persist user" >>/etc/doas.conf
log "doas configured successfully. /etc/doas.conf updated."
}
# Resolve a user-selected checkout without CDPATH output or a symlinked root.
canonical_target_tree() {
typeset path=$1
while [ "${path%/}" != "$path" ] && [ "$path" != "/" ]; do
path=${path%/}
done
if [ -L "$path" ]; then
error "Refusing symlinked checkout root: $path"
return 1
fi
(
unset CDPATH
cd -- "$path" && pwd -P
)
}
# Main function
main() {
TARGET_KIND=""
NO_PROVISION=0
case "${1:-}" in
--list)
move_to_wip_openbsd_src
list_all_directories
return $?
;;
--copy-only)
[ "$#" -ge 2 ] || {
error "Usage: $0 --copy-only TREE [component ...]"
return 1
}
TARGET_TREE=$(canonical_target_tree "$2") || return 1
shift 2
detect_target_tree || return 1
move_to_wip_openbsd_src
if [ "$#" -eq 0 ]; then
copy_all_directories
else
for DIRECTORY in "$@"; do
validate_selection "$DIRECTORY" || return 1
done
SELECTED_DIRECTORIES="$*"
copy_selected_directories
fi
return $?
;;
--no-provision)
NO_PROVISION=1
shift
[ "$#" -eq 0 ] || {
error "Usage: $0 [--list | --copy-only TREE [component ...] | --no-provision]"
return 1
}
;;
"") ;;
*)
error "Usage: $0 [--list | --copy-only TREE [component ...] | --no-provision]"
return 1
;;
esac
check_root
set_cvsroot
select_repository_to_checkout
checkout_selected_repository
ask_copy_from_wip
if [ "${DO_COPY:-0}" -eq 1 ]; then
move_to_wip_openbsd_src
choose_target_tree
TARGET_TREE=$(canonical_target_tree "$TARGET_TREE") || return 1
detect_target_tree || return 1
if [ "${COPY_ALL:-0}" -eq 1 ]; then
copy_all_directories || return 1
elif [ "${COPY_LIST:-0}" -eq 1 ]; then
prompt_selected_directories
copy_selected_directories || return 1
else
list_directories
copy_directory || return 1
fi
else
log "Skipping copy from wip-openbsd-src."
fi
if [ "$NO_PROVISION" -eq 1 ]; then
log "Skipping user creation and doas configuration."
else
create_user_with_random_password
configure_doas
fi
}
# Execute the main function
main "$@"