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.

Trying to reflink or chattr on an sshfs mpunt results in errors

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):

using chattr through sshfs-do.sh

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

making reflinks through sshfs-do.sh

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

spawning a shell on the server using sshfs-do.sh

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

bash
#!/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