func (rootfs *VFS) Dismount(target string) error {
	targetE, err := rootfs.parseurl(target)
	if err != nil {
		return err
	}
	if targetE.Scheme != FSGSMake {
		return gserrors.Newf(ErrURL, "mount target must be vfs url\n%s", target)
	}
	srcE, err := rootfs.meta.queryMount(rootfs, targetE)
	if err != nil {
		if NotFound(err) {
			return nil
		}
		return err
	}
	if srcE == nil {
		return nil
	}
	if err := srcE.userfs.Dismount(rootfs, srcE, targetE); err != nil {
		return err
	}
	return rootfs.meta.dismount(srcE, targetE)
}
Dismount implement RootFS interface
package vfs
import (
	"errors"
	"fmt"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"github.com/gsdocker/gserrors"
	"github.com/gsdocker/gslogger"
	"github.com/gsdocker/gsos/fs"
	"github.com/gsmake/gsmake/property"
)
var (
	ErrURL      = errors.New("error url")
	ErrFS       = errors.New("unknown fs")
	ErrNotFound = errors.New("vfs node not found")
)
func Exists(rootfs RootFS, target string) bool {
	_, _, err := rootfs.Open(target)
	return err == nil
}
func NotFound(err error) bool {
	for {
		if gserror, ok := err.(gserrors.GSError); ok {
			err = gserror.Origin()
			continue
		}
		break
	}
	if err == ErrNotFound {
		return true
	}
	return false
}
const (
	DomainTask     = "task"
	VersionDefault = "current"
	FSGSMake       = "gsmake"
	FSFile         = "file"
)
type Entry struct {
	*url.URL
	Mapping string  
	userfs  UserFS  
}
func (entry *Entry) String() string {
	val, _ := url.QueryUnescape(entry.URL.String())
	return val
}
func (entry *Entry) Domain() string {
	return entry.Query().Get("domain")
}
func (entry *Entry) Name() string {
	return fmt.Sprintf("%s%s", entry.Host, entry.Path)
}
type RootFS interface {
	gslogger.Log
	RootPath() string
	TargetPath() string
	Userspace() string
	Mounted(src string, target string) bool
	Mount(src, target string) error
	Dismount(target string) error
	List(f func(src *Entry, target *Entry) bool) error
	Open(url string) (src *Entry, target *Entry, err error)
	Update(src string, nocache bool) error
	UpdateAll(nocache bool) error
	UpdateCache(src string) error
	Clear() error
	CacheRoot(src *Entry) string
	Cached(src *Entry) error
	Protocol(host string) string
	TempDir(domain string) string
	DomainDir(domain string) string
	Redirect(from, to string, enable bool) error
}
type UserFS interface {
	fmt.Stringer
	Mount(rootfs RootFS, src *Entry, target *Entry) error
	Dismount(rootfs RootFS, src, target *Entry) error
	Update(rootfs RootFS, src *Entry, target *Entry, nocache bool) error
	UpdateCache(rootfs RootFS, cachepath string) error
}
type VFS struct {
	gslogger.Log                    
	userspace    string             
	rootpath     string             
	targetpath   string             
	meta         *Metadata          
	userfs       map[string]UserFS  
}
func New(rootpath string, targetpath string) (RootFS, error) {
	rootfs, err := createRootFS(rootpath, targetpath)
	if err != nil {
		return nil, err
	}
	rootfs.userfs = map[string]UserFS{
		"git":  NewGitFS(),
		"file": NewFileFS(),
	}
	return rootfs, nil
}
func createRootFS(rootpath string, targetpath string) (*VFS, error) {
	log := gslogger.Get("vfs")
	log.D("init vfs ...")
	fullpath, err := filepath.Abs(rootpath)
	if err != nil {
		return nil, gserrors.Newf(err, "get abs path error\n\t%s", rootpath)
	}
	targetfullpath, err := filepath.Abs(targetpath)
	if err != nil {
		return nil, gserrors.Newf(err, "get abs path error\n\t%s", targetpath)
	}
	db, err := newMetadata(fullpath, targetfullpath)
	if err != nil {
		return nil, err
	}
	s := &VFS{
		meta:       db,
		Log:        log,
		userspace:  db.userspace,
		rootpath:   fullpath,
		targetpath: targetfullpath,
	}
	s.D("rootpath :%s", s.rootpath)
	s.D("userspace :%s", s.userspace)
	if !fs.Exists(s.userspace) {
		if err := fs.MkdirAll(s.userspace, 0755); err != nil {
			return nil, gserrors.Newf(err, "create userspace directory error\n%s", s.userspace)
		}
	}
	s.D("init vfs -- success")
	return s, nil
}
func (rootfs *VFS) parseurl(src string) (*Entry, error) {
	u, err := url.Parse(src)
	if err != nil {
		return nil, gserrors.Newf(err, "parse url error:%s", src)
	}
	entry := &Entry{
		URL: u,
	}
	switch entry.Scheme {
	case FSGSMake:
		if entry.Query().Get("domain") == "" {
			return nil, gserrors.Newf(ErrURL, "expect domain :%s", src)
		}
		entry.Mapping = filepath.Join(rootfs.userspace, entry.Query().Get("domain"), "src", entry.Host, entry.Path)
	case FSFile:
		userfs, ok := rootfs.userfs[entry.Scheme]
		if !ok {
			return nil, gserrors.Newf(ErrURL, "unknown fs type :%s", entry.Scheme)
		}
		entry.userfs = userfs
	default:
		if entry.Query().Get("version") == "" {
			return nil, gserrors.Newf(ErrURL, "expect version :%s", src)
		}
		if entry.Query().Get("remote") == "" {
			site, ok := rootfs.meta.site(entry.Host)
			if !ok {
				return nil, gserrors.Newf(ErrURL, "expect remote url :%s", src)
			}
			matcher, err := regexp.Compile(site.Pattern)
			if err != nil {
				return nil, gserrors.Newf(err, "compile site(%s) import path regexp error", entry.Host)
			}
			url := fmt.Sprintf("%s%s", entry.Host, entry.Path)
			m := matcher.FindStringSubmatch(url)
			if m == nil {
				return nil, gserrors.Newf(ErrURL, "expect remote url :%s", src)
			}
			properties := property.Properties{}
			for i, name := range matcher.SubexpNames() {
				if name != "" && properties[name] == nil {
					properties[name] = m[i]
				}
			}
			if entry.RawQuery == "" {
				entry.RawQuery = "remote=" + properties.Expand(site.URL)
			} else {
				entry.RawQuery = entry.RawQuery + "&remote=" + properties.Expand(site.URL)
			}
		}
		userfs, ok := rootfs.userfs[entry.Scheme]
		if !ok {
			return nil, gserrors.Newf(ErrURL, "unknown fs type :%s", entry.Scheme)
		}
		entry.userfs = userfs
	}
	return entry, nil
}
func (rootfs *VFS) RootPath() string {
	return rootfs.rootpath
}
func (rootfs *VFS) TargetPath() string {
	return rootfs.targetpath
}
func (rootfs *VFS) Userspace() string {
	return rootfs.userspace
}
func (rootfs *VFS) Mount(src, target string) error {
	rootfs.D("mount src : %s", src)
	if to, ok := rootfs.meta.queryredirect(src); ok {
		rootfs.D("redirect \n\tfrom :%s\n\tto :%s", src, to)
		src = to
	}
	if err := rootfs.Dismount(target); err != nil {
		return err
	}
	srcE, err := rootfs.parseurl(src)
	if err != nil {
		return err
	}
	targetE, err := rootfs.parseurl(target)
	if err != nil {
		return err
	}
	if srcE.userfs == nil {
		return gserrors.Newf(ErrURL, "mount source can't be vfs url\n%s", src)
	}
	if targetE.Scheme != FSGSMake {
		return gserrors.Newf(ErrURL, "mount target must be vfs url\n%s", src)
	}
	if err := srcE.userfs.Mount(rootfs, srcE, targetE); err != nil {
		return err
	}
	return rootfs.meta.mount(srcE, targetE)
}