Changing attributes and making reflinks over sshfs

SSHFS is a very useful tool: it allows mounting a remote server's filesystem locally over an ssh connection, with zero server-side configuration.

However, it comes with a pitfall: "advanced" filesystem operations like changing attributes or creating copy-on-write copies (reflinks) is not supported.

To the extent of my knowledge, there actually isn't a single remote filesystem solution that supports these features well. Even Linux's native NFS lacks these features.

Fortunately, there is a workaround that we can use: if we have SSH access to the server (which is a given if we're already using SSHFS), we can just run the commands directly on the server.

To make this process easier, I made a script called sshfs-do.sh. It's very easy to use: just execute sshfs-do.sh <command> while inside of an SSHFS mount, and <command> will be run on the server hosting that mount.

So, for example, to make a file immutable with the chattr command, you would run sshfs-do.sh chattr +i file.txt (and sshfs-do.sh chattr -i file.txt to make it mutable again):

Similarly, run sshfs-do.sh cp --reflink=always source dest to make a copy-on-write copy:

Running sshfs-do.sh on its own spawns an interactive shell to the server:

If you want to make use of my sshfs-do.sh script, the full code is below:

#!/bin/bash

pwd=$(pwd)

# Find the root of this mountpoint
mountpoint=$(df -P "$pwd" | awk 'NR==2 {print $6}')

# Sanity check: `mountpoint` must report this as a mountpoint
if ! (mountpoint "$mountpoint" > /dev/null)
then
	echo "What the hell"
	exit 1
fi

mount_entry=$(mount | grep "$mountpoint")

# Check if the mount is sshfs
if [[ ! "$mount_entry" == *"type fuse.sshfs"* ]]
then
	echo "$mountpoint -- Not an sshfs mount"
	exit 1
fi

remote_info=$(echo "$mount_entry" | awk '{print $1}')

# Check that info about the server could be extracted
if [ -z "$remote_info" ]; then
	echo "No mount found for $mountpoint"
	exit 1
fi

# Get user and host from the remote info
user_host=${remote_info%%:*}
host=${user_host#*@}
remote_path=${remote_info#*:}
relative_path="${pwd#${mountpoint}}"

# If command is given, run it.
# If no command is given, spawn a shell
if [ -z "$1" ]
then
	command=sh
else
	command="$@"
fi

# If no mountpoint given to sshfs,
# it will mount the home directory
if [ -z "$remote_path" ]
then
	# In single quotes so that $HOME will resolve on the server
	remote_path='$HOME'
fi

# Connect to server, cd to the correct directory,
# and run the command
ssh -t "$user_host" "cd \"$remote_path/$relative_path\" && $command"

Reuse ssh connections with controlmaster

If you're using password authentication to access the server, it can be a little annoying to type in the password every time. To fix this, we can enable the Controlmaster feature in the openssh client.

Controlmaster works by checking if a connection to the server already exists before opening a new one, and, if it does, openssh will reuse that connection, meaning that you don't need to authenticate again.

To enable controlmaster, simply add the following to ~/.ssh/config:

Host *
   ControlMaster auto
   ControlPath ~/.ssh/master-socket/%r@%h:%p
   ControlPersist 3s

Then, make sure that the socket directory exists:

mkdir ~/.ssh/master-socket