# shellcheck shell=bash
# Version ID Block (comment-only -- NOT executable). Relies on +k filetype
# modifier. This is a sourced library; a real 'declare' here would clobber
# the sourcing script's own $Version (confirmed: they share global scope).
# show_versions() greps this line's text directly; it is never evaluated.
# VersionID='$Id: //p4-sdp/r26.1.0/Server/Unix/p4/common/lib/utils.lib#1 $ $Change: 33565 $'
#------------------------------------------------------------------------------
# Function: show_versions
#
# Show the version of our script plus every *.lib file it actually sources
# (auto-detected by grepping the running script's own 'source'/'.' lines,
# rather than a manually maintained list, so it can't drift out of sync),
# plus anything additionally named in $BASH_LIBS (an optional supplement,
# for cases where a lib can't be auto-detected this way, e.g. it's sourced
# indirectly or its path is built up across several lines).
#
# For each file, prefer a 'VersionID=' line if present (whether a real
# 'declare' in a script, or the comment-only marker used in library files
# -- see the Version Identification section of the SDP Bash Coding
# Standard for why libraries use a comment-only marker: sourcing a library
# that 'declare'd its own $Version/$VersionID/etc. would silently clobber
# the sourcing script's own, since sourced code shares the caller's
# variable scope). Version is derived from the VersionID text via
# grep/string-processing, NOT by sourcing the file. Falls back to the
# legacy hardcoded "Version=" def'n (or "declare Version=") for
# not-yet-migrated files. Silently ignores files that identify their
# version neither way.
function show_versions
{
declare v=
declare vid=
declare vstream=
declare vcl=
declare lib=
declare libpath=
declare -a Libs=()
# shellcheck disable=SC2016
while IFS= read -r lib; do
libpath=$(eval echo "$lib" 2>/dev/null)
[[ -n "$libpath" ]] && Libs+=("$libpath")
done < <(grep -oE '(source|\. )[[:space:]]+"?\$?\{?[A-Za-z0-9_/.${}"]*\.lib' "$0" 2>/dev/null | sed -E 's/^(source|\.)[[:space:]]*"?//')
for bash_lib in "$0" "${Libs[@]}" $BASH_LIBS; do
vid=$(grep -m1 "VersionID=" "$bash_lib" 2>/dev/null)
if [[ -n "$vid" ]]; then
vid=${vid#*\$Id: }
vid=${vid%%\'*}
vstream=${vid#*//}; vstream=${vstream#*/}; vstream=${vstream%%/*}
vcl=${vid##*: }; vcl=${vcl%% *}
v="${vstream}.${vcl}"
[[ "$vstream" == r* ]] || v="${v^^}"
echo "$bash_lib v$v"
continue
fi
# shellcheck disable=SC2196
v=$(egrep -i "^(declare Version|Version)=" "$bash_lib" 2>/dev/null | head -1)
[[ -n "$v" ]] || continue
v=${v#*=}
echo "$bash_lib v$v"
done
echo "BASH_VERSION: $BASH_VERSION"
}
#------------------------------------------------------------------------------
# Function: get_mount_point
#
# Purpose:
# Given a path (file or directory), determine the mount point of the
# filesystem containing it.
#
# Behavior:
# * If the exact path doesn't exist, walks up to the nearest existing parent.
# * Canonicalizes that existing path (prefers `realpath`, else falls back).
# * Uses `findmnt -T` to resolve the mount point (preferred), falling back
# to `df --output=target` if needed.
# * On success: echoes the mount point and returns 0.
# * On error: calls errmsg(), increments ErrorCount (via your function),
# and returns non-zero.
#
# Dependencies (present on RHEL 7+ / Ubuntu 18.04+):
# * `findmnt` (util-linux), `df` and `realpath` (coreutils).
# * msg() and errmsg() functions must be defined by caller.
#
# Usage:
# mp=$(get_mount_point /some/path) || echo "Failed"
#
# Notes:
# * `findmnt -T` correctly handles bind mounts and is preferred.
# * We intentionally canonicalize to avoid surprises with symlinks.
#------------------------------------------------------------------------------
function get_mount_point () {
local input="$1"
if [[ -z "$input" ]]; then
errmsg "No path specified."
return 1
fi
# Walk up until we find an existing path (handles non-existent file paths).
local probe="$input"
while [[ ! -e "$probe" ]]; do
local parent
parent=$(dirname -- "$probe")
if [[ -z "$parent" || "$parent" == "$probe" ]]; then
errmsg "No existing parent found for: $input"
return 1
fi
probe="$parent"
# Stop at root dir even if -e fails (shouldn't in normal cases).
[[ "$probe" == "/" ]] && break
done
# Canonicalize the existing path.
local canon
if command -v realpath >/dev/null 2>&1; then
# -e: require existing; resolves symlinks cleanly
if ! canon=$(realpath -e -- "$probe" 2>/dev/null); then
errmsg "Failed to canonicalize path: $probe"
return 1
fi
elif command -v readlink >/dev/null 2>&1 && readlink -f / >/dev/null 2>&1; then
# Linux readlink with -f behaves like realpath -e
if ! canon=$(readlink -f -- "$probe" 2>/dev/null); then
errmsg "Failed to canonicalize path: $probe"
return 1
fi
else
# Portable fallback: cd + pwd -P (physical)
if ! canon=$(cd "$probe" 2>/dev/null && pwd -P); then
errmsg "Failed to canonicalize path: $probe"
return 1
fi
fi
# Determine mount point: prefer findmnt, fallback to df.
local mount_point=""
if command -v findmnt >/dev/null 2>&1; then
mount_point=$(findmnt -no TARGET -T "$canon" 2>/dev/null)
fi
if [[ -z "$mount_point" ]]; then
# Fallback using df (GNU coreutils)
mount_point=$(df --output=target "$canon" 2>/dev/null | tail -n 1)
fi
if [[ -z "$mount_point" || ! -d "$mount_point" ]]; then
errmsg "Could not determine mount point for: $input"
return 1
fi
msg "$mount_point"
return 0
}
#------------------------------------------------------------------------------
# Function: find_sdp_instance
#
# Purpose:
# Determine the SDP instance name to use.
#
# Behavior:
# * If an explicit instance arg is provided, validates it.
# * Otherwise, scans SDPRoot for subdirs that contain a "bin/" directory.
# - If exactly one candidate is found, echoes it and returns 0.
# - If multiple candidates are found, prefers "1" if present;
# otherwise, selects the lexicographically first (deterministic).
# - If zero are found, emits an error and returns non-zero.
#
# Usage:
# inst=$(find_sdp_instance "/p4" "1") || exit 1
# inst=$(find_sdp_instance "/p4") || exit 1
#------------------------------------------------------------------------------
function find_sdp_instance () {
local sdp_root="${1:-/p4}"
local wanted_inst="${2:-}"
if [[ ! -d "$sdp_root" ]]; then
errmsg "SDPRoot not found or not a directory: $sdp_root"
return 1
fi
if [[ -n "$wanted_inst" ]]; then
if [[ -d "$sdp_root/$wanted_inst/bin" ]]; then
msg "$wanted_inst"
return 0
fi
errmsg "Instance '$wanted_inst' not found under $sdp_root (missing $sdp_root/$wanted_inst/bin)."
return 1
fi
# Discover candidates: any subdir containing bin/
local -a candidates=()
while IFS= read -r -d '' bin_dir; do
candidates+=("$(basename -- "$(dirname -- "$bin_dir")")")
done < <(find "$sdp_root" -mindepth 2 -maxdepth 2 -type d -name bin -print0 2>/dev/null)
case "${#candidates[@]}" in
0)
errmsg "No SDP instances found under $sdp_root (no */bin directories)."
return 1
;;
1)
msg "${candidates[0]}"
return 0
;;
*)
# Prefer instance "1" if present
local inst
for inst in "${candidates[@]}"; do
if [[ "$inst" == "1" ]]; then
msg "1"
return 0
fi
done
# Otherwise, pick lexicographically first (stable/deterministic)
# shellcheck disable=SC2207
candidates=($(printf '%s\n' "${candidates[@]}" | LC_ALL=C sort))
msg "${candidates[0]}"
return 0
;;
esac
}
#------------------------------------------------------------------------------
# Function: find_p4depots_probe_path
#
# Purpose:
# Given SDPRoot and (optionally) an instance, echo a path that resides on the
# P4Depots volume. This path is suitable to pass to get_mount_point().
#
# Behavior:
# * Validates SDPRoot.
# * Determines instance:
# - If explicit instance is given, validates it (requires <inst>/bin).
# - Else scans SDPRoot for instances (subdirs with a "bin/" dir).
# If multiple are found, prefers "1"; otherwise picks the
# lexicographically first (deterministic).
# * Checks, in order of preference, under the chosen instance:
# 1) depots/ (server)
# 2) cache/ (proxy)
# 3) first matching checkpoints*/ (server variants)
# 4) logs/ (broker)
# Echoes the first that exists (dir or symlink), with a trailing slash.
# * If none of the above exist, emits an error and returns non-zero.
#
# Usage:
# probe=$(find_p4depots_probe_path "/p4" "1") || exit 1
# probe=$(find_p4depots_probe_path "/p4") || exit 1
#
# Notes:
# * No fallback to $SDPRoot/$Instance/; fail-fast if structure is unexpected.
#------------------------------------------------------------------------------
function find_p4depots_probe_path () {
local sdp_root="${1:-/p4}"
local inst="${2:-}"
if [[ ! -d "$sdp_root" ]]; then
errmsg "SDPRoot not found or not a directory: $sdp_root"
return 1
fi
# If instance provided, validate it.
if [[ -n "$inst" ]]; then
if [[ ! -d "$sdp_root/$inst/bin" ]]; then
errmsg "Instance '$inst' not found under $sdp_root (missing $sdp_root/$inst/bin)."
return 1
fi
else
# Discover instances: any subdir containing bin/
local -a candidates=()
local bin_dir
while IFS= read -r -d '' bin_dir; do
candidates+=("$(basename -- "$(dirname -- "$bin_dir")")")
done < <(find "$sdp_root" -mindepth 2 -maxdepth 2 -type d -name bin -print0 2>/dev/null)
# If there are no instances, return an error. If there is exactly one, than that is
# the instance. If there are multiple, pick instance '1' (the default) if it exists,
# otherwise the first valid one.
case "${#candidates[@]}" in
0)
errmsg "No SDP instances found under $sdp_root (no */bin directories)."
return 1
;;
1)
inst="${candidates[0]}"
;;
*)
# Prefer "1" if present
local cand
for cand in "${candidates[@]}"; do
if [[ "$cand" == "1" ]]; then
inst="1"
break
fi
done
# Otherwise pick lexicographically first (deterministic)
if [[ -z "$inst" ]]; then
# shellcheck disable=SC2207
candidates=($(printf '%s\n' "${candidates[@]}" | LC_ALL=C sort))
inst="${candidates[0]}"
fi
;;
esac
fi
local base="$sdp_root/$inst"
if [[ ! -d "$base" ]]; then
errmsg "Instance directory missing: $base"
return 1
fi
# Helper: echo path with ensured trailing slash.
local _echo_with_slash
_echo_with_slash() {
local p="$1"
if [[ "$p" == */ ]]; then
msg "$p"
else
msg "$p/"
fi
}
# 1) depots/
if [[ -e "$base/depots" ]]; then
_echo_with_slash "$base/depots"
return 0
fi
# 2) cache/ (proxy)
if [[ -e "$base/cache" ]]; then
_echo_with_slash "$base/cache"
return 0
fi
# 3) checkpoints*/ (handle checkpoints and checkpoints.* variants)
local cp
shopt -s nullglob
for cp in "$base"/checkpoints "$base"/checkpoints.*; do
if [[ -e "$cp" ]]; then
_echo_with_slash "$cp"
shopt -u nullglob
return 0
fi
done
shopt -u nullglob
# 4) logs - last resort, should exist for all server types, even standaloen brokers.
if [[ -e "$base/logs" ]]; then
_echo_with_slash "$base/logs"
return 0
fi
errmsg "No suitable P4Depots-related path found under $base (expected depots/, cache/, or checkpoints*/)."
return 1
}
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #1 | 33565 | Claude (AI Agent by Anthropic) | Initial population of r26.1.0 from main. | ||
| //p4-sdp/main/Server/Unix/p4/common/lib/utils.lib | |||||
| #1 | 33433 | Claude (AI Agent by Anthropic) |
Copy Up from //p4-sdp/dev into //p4-sdp/main. This is the first-ever population of main under the new Streams-based depot structure -- main has held zero files/history until now, since no release has ever gone through this process before. 463 files, covering the entire 2026.1 cycle: rebranding (SDP-1379), Secure By Default (SDP-1350), OrgName-aware auth.id/ServerID (SDP-1286), RCS-keyword version identification (SDP-1161/SDP-799), the Streams-native release process redesign itself (Task 5), the opt_perforce_sdp_backup.sh false-error fix, the P4D 2026.1 test-suite targeting, refreshed P4*.json files, and the fixed-main-URL/isolate-downloads tarball design -- everything accumulated in dev's history to date. Isolated paths (ai_dev_support/, Version, doc/*.html, doc/*.pdf, doc/gen/*.man.txt, doc/gen/sdp_install.cfg, Unsupported/doc/*.html, Unsupported/doc/*.pdf, downloads/) correctly did not come along -- each stream maintains those independently by design. Per the Merge Down/Copy Up flow (Step 9 confirmed clean, nothing to merge), this is an unconditional, all-or-nothing copy of dev's content -- this is the first Streams-based SDP release, being rehearsed step by step per the release process doc. Agent: Claude Code, Model: Claude Sonnet 5 (claude-sonnet-5), operating as bot_Claude_Anthropic. |
||
| //p4-sdp/dev/Server/Unix/p4/common/lib/utils.lib | |||||
| #2 | 33409 | Claude (AI Agent by Anthropic) |
Copy Up from //p4-sdp/dev_rebrand into //p4-sdp/dev. This is the first promotion of dev_rebrand's work into dev since dev_rebrand was created (2025-05-24) -- 303 files, covering the entire 2026.1 rebranding effort (SDP-1379), the Secure By Default adaptation (SDP-1350), OrgName-aware auth.id/ServerID (SDP-1286), RCS-keyword version identification (SDP-1161/SDP-799), and the Streams-native release process redesign (Task 5) done this session, plus everything else accumulated in dev_rebrand's history before this session. Per the Merge Down/Copy Up flow, this is intentionally a full, unconditional blast-replace of dev's content from dev_rebrand -- all selectivity/care happened in the preceding Merge Down (dev -> dev_rebrand, changes 33407-33408), which absorbed Robert Cowham's independent dev-side work first so nothing of his is lost by this Copy Up. Two files are worth calling out since they might look alarming in isolation: - tools/mdcu.sh is deleted -- intentional, retired this session in favor of the two direct Streams commands now documented in doc/ReleaseProcessOverview.md. - tools/ReleaseProcessOverview.md is deleted -- this is a stale relic of a file move dev_rebrand made back in 2025-05-24 (tools/ -> doc/) that was never previously propagated to dev; the current, fully-rewritten doc/ReleaseProcessOverview.md is added/updated correctly by this same changelist. |
||
| #1 | 31913 | C. Thomas Tyler |
p4 merge -b SDP_Classic_to_Streams p4 resolve -as |
||
| //guest/perforce_software/sdp/dev/Server/Unix/p4/common/lib/utils.lib | |||||
| #2 | 31911 | C. Thomas Tyler | Fixed issue with opt_perforce_sdp_backup.sh operating on a standalone broker. | ||
| #1 | 31886 | C. Thomas Tyler |
Refactoring non-trivial functions into a library utils.lib. Added test script. |
||