標準愚痴出力

個人的なIT作業ログです。もしかしたら一般的に参考になることが書いているかもしれません(弱気

Windows でジャンクション作れない

【次回】 | 【完成品】

junction_go.run:

package main

import (
    "os"
    "unsafe"
    "errors"

    "golang.org/x/sys/windows"
    "golang.org/x/xerrors"
)

const (
    _MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16384
    _FSCTL_SET_REPARSE_POINT          = 589988
    _INVALID_HANDLE_VALUE = ^windows.Handle(0)
)

type _MountPointInfo struct {
    Tag                   uint32
    DataLength            uint16
    Reserved              uint16
    TargetOffset          uint16
    TargetByteLength      uint16
    DescriptionOffset     uint16
    DescriptionByteLength uint16
    Buffer                [(_MAXIMUM_REPARSE_DATA_BUFFER_SIZE - 4 - 2*6) / 2]uint16
}

func MountPointCreate(mountPointPath, target string) error {
    _mountPointPath, err := windows.UTF16FromString(mountPointPath)
    if err != nil {
        return xerrors.Errorf("UTF16FromString(%s): %v", mountPointPath, err)
    }
    _target, err := windows.UTF16FromString(target)
    if err != nil {
        return xerrors.Errorf("UTF16FromString(%s): %v", target, err)
    }
    var info _MountPointInfo

    info.Tag = windows.IO_REPARSE_TAG_MOUNT_POINT

    info.TargetOffset = 0
    info.TargetByteLength = uint16(len(_target) * 2 )
    for i := 0 ; i < len(_target) ; i++ {
        info.Buffer[i] = _target[i]
    }
    // copy(info.Buffer[:], _target)
    info.Buffer[len(_target)] = 0

    info.DescriptionOffset = uint16((len(_target)+1)  * 2)
    info.DescriptionByteLength = 0

    info.DataLength = uint16(8 + (len(_target)+1+1)*2)

    err = windows.CreateDirectory(&_mountPointPath[0], nil)
    if err != nil {
        return xerrors.Errorf("windows.CreateDirectory(%s): %v", mountPointPath, err)
    }

    handle, err := windows.CreateFile(&_mountPointPath[0],
        windows.GENERIC_WRITE,
        0,
        nil,
        windows.OPEN_EXISTING,
        windows.FILE_FLAG_BACKUP_SEMANTICS,
        0)
    if err != nil {
        return xerrors.Errorf("windows.CreateFile(%s): %v", mountPointPath, err)
    }
    if handle == _INVALID_HANDLE_VALUE {
        return errors.New("windows.CreateFile: invalid handle value")
    }
    defer windows.CloseHandle(handle)

    var size uint32

    err = windows.DeviceIoControl(
        handle,
        _FSCTL_SET_REPARSE_POINT,
        (*byte)(unsafe.Pointer(&info)),
        uint32(8+info.DataLength),
        nil,
        0,
        &size,
        nil)

    if err != nil {
        return xerrors.Errorf("windows.DeviceIoControl: %v", err)
    }
    return nil
}

func main() {
    if len(os.Args) < 3 {
        println("go run junction.go DST SRC")
        return
    }
    if err := MountPointCreate(os.Args[1], os.Args[2]); err != nil {
        println(err.Error())
        os.Exit(1)
    }
}

これ、動作するけど、ジャンクションにならなんだよな…

$ go run junction_run.go Hoge c:\tmp
$ cd Hoge\
chdir Hoge\: The filename, directory name, or volume label syntax is incorrect.
exit status 1
$ ls -l Hoge\
-rw---- 0 Mar 24 17:43:26 Hoge@ -> c:\tmp
$

これ、バッファのサイズとか、ちょっとでも触るとAPIがエラーを返してくる。で、現在のソースだと実行は成功するんだけど、できたディレクトリはジャンクションとして機能しない。リパースポイント先は記録されているのに(nyagos の ls で表示できるから)

うーん、なんでだろ(というか、この記事は事実上、あきらめの儀式である)

参考リンク

追記

実は、2時間後に解決したのだった…