[go-nuts] Realpath?

1,362 views
Skip to first unread message

Taru Karttunen

unread,
Apr 22, 2010, 9:18:09 AM4/22/10
to golan...@googlegroups.com
Hello

Has anyone written a realpath(3) replacement in Go?

- Taru Karttunen


--
Subscription settings: http://groups.google.com/group/golang-nuts/subscribe?hl=en

Michael Hoisie

unread,
Apr 22, 2010, 9:21:01 AM4/22/10
to golang-nuts
Check out the Clean function in http://golang.org/pkg/path/

Evan Shaw

unread,
Apr 22, 2010, 9:28:28 AM4/22/10
to Michael Hoisie, golang-nuts
On Thu, Apr 22, 2010 at 8:21 AM, Michael Hoisie <hoi...@gmail.com> wrote:
> Check out the Clean function in http://golang.org/pkg/path/

That's close, but not quite the same thing since it doesn't take
symlinks into account. I think realpath could be added to the syscall
package easily enough.

- Evan

Taru Karttunen

unread,
Apr 22, 2010, 10:21:42 AM4/22/10
to Evan Shaw, Michael Hoisie, golang-nuts
On Thu, 22 Apr 2010 08:28:28 -0500, Evan Shaw <chick...@gmail.com> wrote:
> That's close, but not quite the same thing since it doesn't take
> symlinks into account. I think realpath could be added to the syscall
> package easily enough.

The symlink functionality is the most important part for me and
path.Clean misses that. realpath is implemented in libc without
any syscalls so using a syscall is not possibility.

Just wanting to avoid the hassle of reinventing and debugging
the wheel again.

- Taru Karttunen

Evan Shaw

unread,
Apr 22, 2010, 10:31:10 AM4/22/10
to Taru Karttunen, Michael Hoisie, golang-nuts
On Thu, Apr 22, 2010 at 9:21 AM, Taru Karttunen <tar...@taruti.net> wrote:
> The symlink functionality is the most important part for me and
> path.Clean misses that. realpath is implemented in libc without
> any syscalls so using a syscall is not possibility.

Oh, nuts, you're right. I guess if we want this functionality then
someone's going to have to reinvent the wheel (or find a version with
a permissive license that can be adapted to Go).

- Evan

Taru Karttunen

unread,
Apr 22, 2010, 2:55:51 PM4/22/10
to golan...@googlegroups.com
Hello

Attached a preliminary implementation.

realpath.go

Koala Yeung

unread,
Jul 16, 2012, 4:25:49 AM7/16/12
to golan...@googlegroups.com
Hello all,

Made some more changes and put it on Github:
https://github.com/yookoala/realpath

Please tell me if there is anything needed to be changed.

Koala Yeung


Koala Yeung於 2012年7月16日星期一UTC+8下午3時48分39秒寫道:
Hello all,

Thanks Taru for the implementation. It seems the code fails with Go 1
I've changed it so it can work in Go 1 (Not sure if I got it right)

package realpath

import (
  "os"
  "fmt"
  "bytes"
)

func Realpath(filepath string) (string, error) {

  if len(filepath)==0 { return "", os.ErrInvalid }

  sepStr := string(os.PathSeparator)

  if filepath[0] != os.PathSeparator {
    pwd, err := os.Getwd()
    if err!=nil { return "", err }
    filepath = pwd + sepStr + filepath
  }

  path   := []byte(filepath)
  nlinks := 0
  start  := 1
  prev   := 1
  for start < len(path) {
    c   := nextComponent(path, start)
    cur := c[start:]

    switch {

      case len(cur)==0:
        copy(path[start:], path[start+1:])
        path = path[0:len(path)-1]

      case len(cur)==1 && cur[0]=='.':
        if start+2 < len(path) { copy(path[start:], path[start+2:]) }
        path = path[0:len(path)-2]

      case len(cur)==2 && cur[0]=='.' && cur[1] == '.':
        copy(path[prev:], path[start+2:])
        path = path[0:len(path)+prev-(start+2)]
        prev  = 1
        start = 1

      default:

        fi, err := os.Lstat(string(c))
        if err!=nil { return "", err }
        if fi.Mode() & os.ModeSymlink == os.ModeSymlink {

          nlinks++
          if nlinks > 16 { return "", os.ErrInvalid }

          var dst string
          dst, err = os.Readlink(string(c))
          fmt.Printf("SYMLINK -> %s\n", dst)

          rest := string(path[len(c):])
          if dst[0] == '/' {
            // Absolute links
            path  = []byte(dst + "/" + rest)
          } else {
            // Relative links
            path  = []byte(string(path[0:start])+dst+"/"+rest)
          }
          prev  = 1
          start = 1
        } else {
          // Directories
          prev  = start
          start = len(c) + 1
        }
    }
  }

  for len(path)>1 && path[len(path)-1]=='/' { path = path[0:len(path)-1] }
  return string(path), nil

}

func nextComponent(path []byte, start int) []byte {
  v := bytes.IndexByte(path[start:], '/')
  if v < 0 { return path }
  return path[0 : start+v]
}


Please feel free to comment on the code
Thanks.

Koala Yeung


Taru Karttunen於 2010年4月23日星期五UTC+8上午2時55分51秒寫道:
Hello

Attached a preliminary implementation.


package realpath

import (
        "fmt"
        "os"
        "bytes"
        )

func RealPath(filepath string) (res string, err os.Error) {
        if len(filepath)==0 { return "", os.EIO }

        if filepath[0] != '/' {
                pwd, err := os.Getwd()
                if err!=nil { return }
                filepath = pwd + "/" + filepath
        }
        
        path   := []byte(filepath)
        nlinks := 0
        start  := 1
        prev   := 1
        for start < len(path) {
                c   := nextComponent(path, start)
                cur := c[start:]
//                fmt.Printf("Loop start %2d @ c '%s' - path %s\n", start, c, path)
//                fmt.Printf("path[start:] = '%s'\n", path[start:])
//                fmt.Printf("cur          = '%s'\n", cur)
                switch {
                case len(cur)==0:
                        copy(path[start:], path[start+1:])
                        path = path[0:len(path)-1]
                case len(cur)==1 && cur[0]=='.':
                        if start+2 < len(path) { copy(path[start:], path[start+2:]) }
                        path = path[0:len(path)-2]
                case len(cur)==2 && cur[0]=='.' && cur[1] == '.':
                        copy(path[prev:], path[start+2:])
                        path = path[0:len(path)+prev-(start+2)]
                        prev  = 1
                        start = 1
                default:
                
                        fi, err := os.Lstat(string(c))
                        if err!=nil { return }
                        if fi.IsSymlink() {
                                
                                nlinks++
                                if nlinks > 16 { return "", os.EIO }
                                
                                var dst string
                                dst, err = os.Readlink(string(c))
                                fmt.Printf("SYMLINK -> %s\n", dst)
                                
                                rest := string(path[len(c):])
                                if dst[0] == '/' {
                                        // Absolute links
                                        path  = []byte(dst + "/" + rest)
                                } else {
                                        // Relative links
                                        path  = []byte(string(path[0:start])+dst+"/"+rest)
                                        
                                }
                                prev  = 1
                                start = 1
                        } else {
                                // Directories
                                prev  = start
                                start = len(c) + 1
                        }
                }
        }
        for len(path)>1 && path[len(path)-1]=='/' { path = path[0:len(path)-1] }
//        fmt.Printf(" -> %s\n", path)
        return string(path), nil
}

func nextComponent(path []byte, start int) []byte {
        v := bytes.IndexByte(path[start:], '/')
        if v < 0 { return path }
        return path[0 : start+v]
}

Rémy Oudompheng

unread,
Jul 16, 2012, 6:55:53 AM7/16/12
to Koala Yeung, golang-nuts
Did you have a look at filepath.EvalSymlinks?

Rémy.

rgo...@redhat.com

unread,
Jan 18, 2018, 1:28:09 PM1/18/18
to golang-nuts


On Monday, 16 July 2012 13:55:53 UTC+3, Rémy Oudompheng wrote:
Did you have a look at filepath.EvalSymlinks?

Rémy.

Confirmed to be working. Thanks Remy.

Carlos Henrique Guardão Gandarez

unread,
Aug 3, 2023, 2:57:08 PM8/3/23
to golang-nuts
Hey there!

I created a new lib to return a real path in Go.

Thanks.

TheDiveO

unread,
Aug 4, 2023, 4:14:18 AM8/4/23
to golang-nuts
As I couldn't figure this out from the repo's documentation: what's the difference and what's the benefit compared to stdlib https://pkg.go.dev/path/filepath#EvalSymlinks?

TheDiveO

unread,
Aug 4, 2023, 4:26:32 AM8/4/23
to golang-nuts
Also, your function (as well as the one you seem to have copied and modified, did I get that right from your documentation?) will most probably not work correctly if there are symlinks "inside" the path and not just at the beginning of the given path. There's a reason to my limited understanding why the stdlib EvalSymlinks function has to iterate, in order to cover finding multiple symlinks along the path, and a previous symlink will influence later symlinks.

Some time ago, I had to get deeper into stdlib's EvalSymlinks in order to come up with a modified one that allows the path to be interpreted relative to an arbitrary other "root" path, even if the path to be resolved is seemingly absolute. It's a special use case when using Linux and doing funny mount namespace-related VFS accesses through Linux' process filesystem, and its "root" elements in particular. In case someone wants to know more, here we are: https://github.com/thediveo/procfsroot ... I'm using this to access the VFS view of containers without needing to spawn new processes, switching them into container mount namespaces, and then all the hassles of shuttling commands and responses forth and back. Instead, I resolve a path that is "absolute" to another mount namespace as relative to a suitable process filesystem entry and then can access the file/directory/etc. directly by the "resolved" path, using normal VFS syscalls from any arbitrary Go routine (and OS-level thread/task).
Reply all
Reply to author
Forward
0 new messages