With the symlink()
function, the BSD (Mac OS X) man page says:
int symlink(const char *path1, const char *path2);
DESCRIPTION
A symbolic link path2
is created to path1
(path2
is the name of the file created, path1
is the string used in creating the symbolic link). Either name may be an arbitrary path name; the files need not be on the same file system.
Note that what you specify as path1
is used, verbatim, as the content of the symlink. Thus, to make the symlink accurately, you have to get the relative path correct. That is, the name passed as path1
must be the correct name relative to path2
.
In other words, you can't completely avoid the process of mapping the names, with all the attendant difficulties.
I have a Perl script, relpath
, that I cobbled together from the answer to Convert absolute path into relative path given a current directory and a fairly old news group message from comp.unix.shell. Converting some of this to C is not beyond the wit of mankind. In fact, it is bound to have been done numerous times. The difficulty will be finding the code.
relpath
#!/usr/bin/env perl
#
# @(#)$Id: relpath.pl,v 1.4 2014/12/08 18:23:17 jleffler Exp $
#
# Usage: relpath source target [...]
#
# Purpose: Print relative path of target w.r.t. source
#
# Based loosely on code from:
# http://unix.derkeiler.com/Newsgroups/comp.unix.shell/2005-10/1256.html
# Via: https://stackoverflow.com/questions/2564634
use strict;
use warnings;
use File::Basename;
use Cwd qw(realpath getcwd);
if (scalar @ARGV < 2)
{
my $arg0 = basename($0, ".pl");
die "Usage: $arg0 from to [...]\n"
}
my $pwd;
my $verbose = 0;
# Fettle filename so it is absolute.
# Deals with '//', '/./' and '/../' notations, plus symlinks.
# The realpath() function does the hard work if the path exists.
# For non-existent paths, the code does a purely textual hack.
sub resolve
{
my($name) = @_;
my($path) = realpath($name);
if (!defined $path)
{
# Path does not exist - do the best we can with lexical analysis
# Assume Unix - not dealing with Windows.
$path = $name;
if ($name !~ m%^/%)
{
$pwd = getcwd if !defined $pwd;
$path = "$pwd/$path";
}
$path =~ s%//+%/%g; # Not UNC paths.
$path =~ s%/$%%; # No trailing /
$path =~ s%/\./%/%g; # No embedded /./
# Try to eliminate /../abc/
$path =~ s%/\.\./(?:[^/]+)(/|$)%$1%g;
$path =~ s%/\.$%%; # No trailing /.
$path =~ s%^\./%%; # No leading ./
# What happens with . and / as inputs?
}
return($path);
}
sub print_result
{
my($source, $target, $relpath) = @_;
if ($verbose)
{
print "source = $ARGV[0]\n";
print "target = $ARGV[1]\n";
print "relpath = $relpath\n";
}
else
{
print "$relpath\n";
}
}
# Nasty!
my($source) = resolve($ARGV[0]);
my(@source) = split '/', $source;
shift @ARGV;
sub relpath
{
my($name) = @_;
my($target) = resolve($name);
print_result($source, $target, ".") if ($source eq $target);
# Split!
my(@target) = split '/', $target;
my $count = scalar(@source);
$count = scalar(@target) if (scalar(@target) < $count);
my $relpath = "";
my $i;
# Both paths are absolute; Perl splits an empty field 0.
for ($i = 1; $i < $count; $i++)
{
last if $source[$i] ne $target[$i];
}
for (my $s = $i; $s < scalar(@source); $s++)
{
$relpath = "$relpath/" if ($s > $i);
$relpath = "$relpath..";
}
for (my $t = $i; $t < scalar(@target); $t++)
{
$relpath = "$relpath/" if ($relpath ne "");
$relpath = "$relpath$target[$t]";
}
print_result($source, $target, $relpath);
}
foreach my $target (@ARGV)
{
relpath($target);
}
test.relpath
NB: This requires relpath.pl
rather than just relpath
.
#!/bin/ksh
#
# @(#)$Id: test.relpath.sh,v 1.1 2010/04/25 15:19:20 jleffler Exp $
#
# Test relpath Perl script fairly exhaustively
# BUG: should include expected answers!
sed 's/#.*//;/^[ ]*$/d' <<! |
/home/part1/part2 /home/part1/part3
/home/part1/part2 /home/part4/part5
/home/part1/part2 /work/part6/part7
/home/part1 /work/part1/part2/part3/part4
/home /work/part2/part3
/ /work/part2/part3/part4
/home/part1/part2 /home/part1/part2/part3/part4
/home/part1/part2 /home/part1/part2/part3
/home/part1/part2 /home/part1/part2
/home/part1/part2 /home/part1
/home/part1/part2 /home
/home/part1/part2 /
/home/part1/part2 /work
/home/part1/part2 /work/part1
/home/part1/part2 /work/part1/part2
/home/part1/part2 /work/part1/part2/part3
/home/part1/part2 /work/part1/part2/part3/part4
home/part1/part2 home/part1/part3
home/part1/part2 home/part4/part5
home/part1/part2 work/part6/part7
home/part1 work/part1/part2/part3/part4
home work/part2/part3
. work/part2/part3
home/part1/part2 home/part1/part2/part3/part4
home/part1/part2 home/part1/part2/part3
home/part1/part2 home/part1/part2
home/part1/part2 home/part1
home/part1/part2 home
home/part1/part2 .
home/part1/part2 work
home/part1/part2 work/part1
home/part1/part2 work/part1/part2
home/part1/part2 work/part1/part2/part3
home/part1/part2 work/part1/part2/part3/part4
!
{
echo "Relative paths (source, target, relative path)"
while read source target
do
echo "$source $target $(${PERL:-perl} relpath.pl $source $target)"
done |
awk '{ printf("%-20s %-30s %s\n", $1, $2, $3); }'
}
Example output
Note that the first relative path, ../part3
, is correct if /home/part1/part2
is a directory, or is assumed to be a directory. This code requires care in interpreting the output. Note that the symlink()
system call does not require that the path1
name refers to an existing file or directory (but, by contrast, path2
must not refer to an existing file or directory, though only the leaf element must not exist; all previous directories must exist).
Relative paths (source, target, relative path)
/home/part1/part2 /home/part1/part3 ../part3
/home/part1/part2 /home/part4/part5 ../../part4/part5
/home/part1/part2 /work/part6/part7 ../../../work/part6/part7
/home/part1 /work/part1/part2/part3/part4 ../../work/part1/part2/part3/part4
/home /work/part2/part3 ../work/part2/part3
/ /work/part2/part3/part4 work/part2/part3/part4
/home/part1/part2 /home/part1/part2/part3/part4 part3/part4
/home/part1/part2 /home/part1/part2/part3 part3
/home/part1/part2 /home/part1/part2 .
/home/part1/part2 /home/part1 ..
/home/part1/part2 /home ../..
/home/part1/part2 / ../../..
/home/part1/part2 /work ../../../work
/home/part1/part2 /work/part1 ../../../work/part1
/home/part1/part2 /work/part1/part2 ../../../work/part1/part2
/home/part1/part2 /work/part1/part2/part3 ../../../work/part1/part2/part3
/home/part1/part2 /work/part1/part2/part3/part4 ../../../work/part1/part2/part3/part4
home/part1/part2 home/part1/part3 ../part3
home/part1/part2 home/part4/part5 ../../part4/part5
home/part1/part2 work/part6/part7 ../../../work/part6/part7
home/part1 work/part1/part2/part3/part4 ../../work/part1/part2/part3/part4
home work/part2/part3 ../work/part2/part3
. work/part2/part3 work/part2/part3
home/part1/part2 home/part1/part2/part3/part4 part3/part4
home/part1/part2 home/part1/part2/part3 part3
home/part1/part2 home/part1/part2 .
home/part1/part2 home/part1 ..
home/part1/part2 home ../..
home/part1/part2 . ../../..
home/part1/part2 work ../../../work
home/part1/part2 work/part1 ../../../work/part1
home/part1/part2 work/part1/part2 ../../../work/part1/part2
home/part1/part2 work/part1/part2/part3 ../../../work/part1/part2/part3
home/part1/part2 work/part1/part2/part3/part4 ../../../work/part1/part2/part3/part4