
I have an application where I want to use relative paths in symlinks (so that rsyncing to a different machine with a different root directory produces something with the same effect). So I want makeRelative "/var/www/this_server/foo/bar/" "/var/www/this_server/bob" to return "../../bob" (which would then be used to create a symlink) so that if I copy the tree below /var/www/this_server to /var/www/that_server (perhaps on a different machine, but copying the symlinks without rewriting them), the symlinks point to the same files. On posix at least, this would permit isRelative (makeRelative a b) always to be True, which is a pleasant property for it to have. Is there any reason that System.FilePath.Posix.makeRelative should not do this? I don’t know enough about non-posix filesystems to know whether it even makes sense elsewhere. -- Jón Fairbairn Jon.Fairbairn@cl.cam.ac.uk

On Wed, Sep 26, 2012 at 03:30:50PM +0100, Jon Fairbairn wrote:
I have an application where I want to use relative paths in symlinks (so that rsyncing to a different machine with a different root directory produces something with the same effect).
So I want
makeRelative "/var/www/this_server/foo/bar/" "/var/www/this_server/bob"
to return "../../bob" (which would then be used to create a symlink) so that if I copy the tree below /var/www/this_server to /var/www/that_server (perhaps on a different machine, but copying the symlinks without rewriting them), the symlinks point to the same files.
On posix at least, this would permit isRelative (makeRelative a b) always to be True, which is a pleasant property for it to have.
Is there any reason that System.FilePath.Posix.makeRelative should not do this? I don’t know enough about non-posix filesystems to know whether it even makes sense elsewhere.
Coincidentally, I needed this yesterday, was also surprised to find it missing, and wrote: makeRelativeTo :: FilePath -> FilePath -> FilePath this `makeRelativeTo` that = directory > thisFilename where (thisDirectory, thisFilename) = splitFileName this thatDirectory = dropFileName that directory = joinPath $ f (splitPath thisDirectory) (splitPath thatDirectory) f (x : xs) (y : ys) | x == y = f xs ys f xs ys = replicate (length ys) ".." ++ xs (in my case, I know that both paths are absolute). One thing I wasn't sure of is what to do about different drives on Windows. Perhaps it would actually need to return a Maybe FilePath? Thanks Ian

Ian Lynagh
On Wed, Sep 26, 2012 at 03:30:50PM +0100, Jon Fairbairn wrote:
I have an application where I want to use relative paths in symlinks […]
So I want
makeRelative "/var/www/this_server/foo/bar/" "/var/www/this_server/bob"
to return "../../bob" […]
On posix at least, this would permit isRelative (makeRelative a b) always to be True, which is a pleasant property for it to have.
Is there any reason that System.FilePath.Posix.makeRelative should not do this? I don’t know enough about non-posix filesystems to know whether it even makes sense elsewhere.
Coincidentally, I needed this yesterday
So evidently it is useful, and at least two people think it should work this way :-)
(in my case, I know that both paths are absolute).
I wrote something similar, but with calls to isAbsolute and use of System.FilePath.makeRelative
One thing I wasn't sure of is what to do about different drives on Windows. Perhaps it would actually need to return a Maybe FilePath?
That would be an API change. Instead the documentation could still have a “where possible” clause, but add that it’s always possible on Posix. -- Jón Fairbairn Jon.Fairbairn@cl.cam.ac.uk

Jon Fairbairn
So I want
makeRelative "/var/www/this_server/foo/bar/" "/var/www/this_server/bob"
to return "../../bob"
[...]
Is there any reason that System.FilePath.Posix.makeRelative should not do this? I don’t know enough about non-posix filesystems to know whether it even makes sense elsewhere.
I don't know whether this might the reason (or whether it is relevant at all for the API at hand) but you can't implement this function properly w/o turning it into an IO action, since you need to be able to introspect the filesystem, because under POSIX at least, you can easily construct a filesystem, which exhibits the following confusing behaviour: ,---- | /tmp/xxx$ pwd | /tmp/xxx | /tmp/xxx$ ls -l bar | total 0 | -rw-rw-r-- 1 hvr hvr 0 Sep 28 22:03 this_is_in_xxx | /tmp/xxx$ cd bar | /tmp/xxx/bar$ ls -l | total 0 | -rw-rw-r-- 1 hvr hvr 0 Sep 28 22:03 this_is_in_xxx | /tmp/xxx/bar$ ls -l ../bar | total 0 | -rw-rw-r-- 1 hvr hvr 0 Sep 28 22:03 this_is_in_xxx | /tmp/xxx/bar$ cd .. | /tmp/xxx$ cd zoo | /tmp/xxx/zoo$ pwd | /tmp/xxx/zoo | /tmp/xxx/zoo$ ls -l ../bar | total 0 | -rw-rw-r-- 1 hvr hvr 0 Sep 28 22:03 this_is_in_foo `---- this is simply because '/tmp/xxx/zoo' is actually a symlink pointing to a different folder '/tmp/foo/zoo', whose parent contains a different 'bar' folder. I.e.: ,---- | $ ls -lR /tmp/foo /tmp/xxx | /tmp/foo: | total 0 | drwxrwxr-x 2 hvr hvr 60 Sep 28 22:03 bar | drwxrwxr-x 2 hvr hvr 40 Sep 28 22:02 zoo | | /tmp/foo/bar: | total 0 | -rw-rw-r-- 1 hvr hvr 0 Sep 28 22:03 this_is_in_foo | | /tmp/foo/zoo: | total 0 | | /tmp/xxx: | total 0 | drwxrwxr-x 2 hvr hvr 60 Sep 28 22:03 bar | lrwxrwxrwx 1 hvr hvr 10 Sep 28 22:04 zoo -> ../foo/zoo | | /tmp/xxx/bar: | total 0 | -rw-rw-r-- 1 hvr hvr 0 Sep 28 22:03 this_is_in_xxx `---- thus, | makeRelative "/tmp/xxx/zoo" "/tmp/xxx/bar" would return "../bar" which when passed to filesystem related system-calls (assuming CWD is /tmp/xxx/zoo) wouldn't point to "/tmp/xxx/bar" but rather to "/tmp/foo/bar" instead. cheers, hvr

Herbert Valerio Riedel
Jon Fairbairn
writes: So I want
makeRelative "/var/www/this_server/foo/bar/" "/var/www/this_server/bob"
to return "../../bob"
[...]
Is there any reason that System.FilePath.Posix.makeRelative should not do this? I don’t know enough about non-posix filesystems to know whether it even makes sense elsewhere.
I don't know whether this might the reason (or whether it is relevant at all for the API at hand) but you can't implement this function properly w/o turning it into an IO action
Ah, well spotted. In my use case I happen to know the structure of the directories, so hadn’t considered that symlinks can make it impossible to do purely. Given that in System.Directory there is makeRelativeToCurrentDirectory:: FilePath -> IO FilePath, we should probably have a function in there makeRelativeToPath:: FilePath -> FilePath -> IO FilePath. and do it properly. Whether this should be makeRelativeToPath:: FilePath -> FilePath -> IO (Maybe FilePath) (cf Ian Lynagh’s post) is another argument. I don’t like relying on exceptions in general, but once we’re in the IO monad (where exceptions seem to be used willy-nilly), throwing something when no relative path is possible seems not entirely unreasonable. -- Jón Fairbairn Jon.Fairbairn@cl.cam.ac.uk

On Fri, Sep 28, 2012 at 9:16 PM, Herbert Valerio Riedel
I don't know whether this might the reason (or whether it is relevant at all for the API at hand) but you can't implement this function properly w/o turning it into an IO action, since you need to be able to introspect the filesystem, because under POSIX at least, you can easily construct a filesystem, which exhibits the following confusing behaviour:
I have two comments on this behaviour: 1. "confusing" is not the same as "wrong" 2. surely this issue can be avoided by using something like System.Directory.canoncalizePath before calling makeRelative. I think doing things that way would separate the pure and IO parts of the algorithm much better, and allow users to ignore the possibility of symlinks when that was appropriate for their use.

On Sun, Sep 30, 2012 at 03:24:14PM +0100, Ben Millwood wrote:
On Fri, Sep 28, 2012 at 9:16 PM, Herbert Valerio Riedel
wrote: I don't know whether this might the reason (or whether it is relevant at all for the API at hand) but you can't implement this function properly w/o turning it into an IO action, since you need to be able to introspect the filesystem, because under POSIX at least, you can easily construct a filesystem, which exhibits the following confusing behaviour:
I have two comments on this behaviour:
1. "confusing" is not the same as "wrong" 2. surely this issue can be avoided by using something like System.Directory.canoncalizePath before calling makeRelative. I think doing things that way would separate the pure and IO parts of the algorithm much better, and allow users to ignore the possibility of symlinks when that was appropriate for their use.
In my use, at least one of the paths didn't actually exist on the filesystem, so canonicalising the path would have failed. Thanks Ian

On 9/30/12 10:24 AM, Ben Millwood wrote:
2. surely this issue can be avoided by using something like System.Directory.canoncalizePath before calling makeRelative. I think doing things that way would separate the pure and IO parts of the algorithm much better, and allow users to ignore the possibility of symlinks when that was appropriate for their use.
Indeed. While it's important to be able to capture correct POSIX behavior, there's also plenty of room for legitimate uses of naive relative paths. In particular, the former is more important when accessing the filesystem, whereas the latter is more important when generating files[1]. So long as the documentation includes das blinkenlights, to ensure that users know which behavior they're getting, I think it's fine to offer the naive implementation. If folks consider it "unsafe" enough to warrant name changes, we could always: canoncalizePath :: Path -> IO Path naiveMakeRelative :: Path -> Path -> Path makeRelative x y = do x <- canoncalizePath x y <- canoncalizePath y return $! naiveMakeRelative x y [1] Because you know the filesystem structure you'll be generating, so you know whether this will be an issue. And because the files you'll be generating don't yet exist and so can't be cannonicalized, as others have mentioned. -- Live well, ~wren
participants (6)
-
Ben Millwood
-
Herbert Valerio Riedel
-
Ian Lynagh
-
Ian Lynagh
-
Jon Fairbairn
-
wren ng thornton