utils.lib #2

  • //
  • guest/
  • perforce_software/
  • sdp/
  • dev/
  • Server/
  • Unix/
  • p4/
  • common/
  • lib/
  • utils.lib
  • View
  • Commits
  • Open Download .zip Download (9 KB)
# shellcheck shell=bash

#------------------------------------------------------------------------------
# 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
#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.