Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 55 additions & 12 deletions relink.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@
"""

import os
import sys
import pwd
import argparse
import logging

DEFAULT_SOURCE_ROOT = '/glade/campaign/cesm/cesmdata/cseg/inputdata/'
DEFAULT_TARGET_ROOT = '/glade/campaign/collections/gdex/data/d651077/cesmdata/inputdata/'

# Set up logger
logger = logging.getLogger(__name__)

def find_and_replace_owned_files(source_dir, target_dir, username):
"""
Finds files owned by a specific user in a source directory tree,
Expand All @@ -29,10 +34,15 @@ def find_and_replace_owned_files(source_dir, target_dir, username):
try:
user_uid = pwd.getpwnam(username).pw_uid
except KeyError:
print(f"Error: User '{username}' not found. Exiting.")
logger.error("Error: User '%s' not found. Exiting.", username)
return

print(f"Searching for files owned by '{username}' (UID: {user_uid}) in '{source_dir}'...")
logger.info(
"Searching for files owned by '%s' (UID: %s) in '%s'...",
username,
user_uid,
source_dir
)

for dirpath, _, filenames in os.walk(source_dir):
for filename in filenames:
Expand All @@ -41,23 +51,28 @@ def find_and_replace_owned_files(source_dir, target_dir, username):
# Use os.stat().st_uid to get the file's owner UID
try:
if os.path.islink(file_path):
print(f"Skipping symlink: {file_path}")
logger.info("Skipping symlink: %s", file_path)
continue

file_uid = os.stat(file_path).st_uid
except FileNotFoundError:
continue # Skip if file was deleted during traversal

if file_uid == user_uid:
print(f"Found owned file: {file_path}")
logger.info("Found owned file: %s", file_path)

# Determine the relative path and the new link's destination
relative_path = os.path.relpath(file_path, source_dir)
link_target = os.path.join(target_dir, relative_path)

# Check if the target file actually exists
if not os.path.exists(link_target):
print(f"Warning: Corresponding file not found in '{target_dir}' for '{file_path}'. Skipping.")
logger.warning(
"Warning: Corresponding file not found in '%s' "
"for '%s'. Skipping.",
target_dir,
file_path
)
continue

# Get the link name
Expand All @@ -66,9 +81,9 @@ def find_and_replace_owned_files(source_dir, target_dir, username):
# Remove the original file
try:
os.rename(link_name, link_name+".tmp")
print(f"Deleted original file: {link_name}")
logger.info("Deleted original file: %s", link_name)
except OSError as e:
print(f"Error deleting file {link_name}: {e}. Skipping.")
logger.error("Error deleting file %s: %s. Skipping.", link_name, e)
continue

# Create the symbolic link, handling necessary parent directories
Expand All @@ -77,18 +92,18 @@ def find_and_replace_owned_files(source_dir, target_dir, username):
os.makedirs(os.path.dirname(link_name), exist_ok=True)
os.symlink(link_target, link_name)
os.remove(link_name+".tmp")
print(f"Created symbolic link: {link_name} -> {link_target}")
logger.info("Created symbolic link: %s -> %s", link_name, link_target)
except OSError as e:
os.rename(link_name+".tmp", link_name)
print(f"Error creating symlink for {link_name}: {e}. Skipping.")
logger.error("Error creating symlink for %s: %s. Skipping.", link_name, e)

def parse_arguments():
"""
Parse command-line arguments.

Returns:
argparse.Namespace: Parsed arguments containing source_root
and target_root.
argparse.Namespace: Parsed arguments containing source_root,
target_root, and verbosity settings.
"""
parser = argparse.ArgumentParser(
description=(
Expand All @@ -111,11 +126,39 @@ def parse_arguments():
)
)

# Verbosity options (mutually exclusive)
verbosity_group = parser.add_mutually_exclusive_group()
verbosity_group.add_argument(
'-v', '--verbose',
action='store_true',
help='Enable verbose output'
)
verbosity_group.add_argument(
'-q', '--quiet',
action='store_true',
help='Quiet mode (show only warnings and errors)'
)

return parser.parse_args()

if __name__ == '__main__':
# --- Configuration ---

args = parse_arguments()

# Configure logging based on verbosity flags
if args.quiet:
LOG_LEVEL = logging.WARNING
elif args.verbose:
LOG_LEVEL = logging.DEBUG
else:
LOG_LEVEL = logging.INFO

logging.basicConfig(
level=LOG_LEVEL,
format='%(message)s',
stream=sys.stdout
)

my_username = os.environ['USER']

# --- Execution ---
Expand Down
Loading