github.com/nvi-inc/fsgo@v0.2.1/main.go (about) 1 // Copyright 2019 NVI Inc. All rights reserved. 2 // Use of this source code is governed by a MIT 3 // license that can be found in the LICENSE file. 4 5 package fs 6 7 import ( 8 "bufio" 9 "bytes" 10 "errors" 11 "fmt" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "regexp" 16 "strconv" 17 "strings" 18 19 versions "github.com/nvi-inc/fsgo/versions" 20 _ "github.com/nvi-inc/fsgo/versions/all" 21 ) 22 23 const DefaultPath = "/usr2/fs" 24 25 type FieldSystem versions.FieldSystem 26 type Rdbe versions.Rdbe 27 28 var ErrVersionNotSupported = errors.New("version not supported") 29 30 // Attach attaches to the shared memory of the Field System installed at 31 // DefaultPath, attempting to automatically detect the version. Returns an 32 // error if the detected version is not supported or the system call fails. 33 func Attach() (fs FieldSystem, err error) { 34 return AttachPath(DefaultPath) 35 } 36 37 // AttachPath attaches to the shared memory of the Field System installed at 38 // path, attempting to automatically detect the version. Returns an error if 39 // the detected version is not supported or the system call fails. 40 func AttachPath(path string) (fs FieldSystem, err error) { 41 version, err := InstalledVersion(path) 42 if err != nil { 43 return nil, err 44 } 45 return AttachVersionPath(version, path) 46 } 47 48 // AttachPath attaches to the shared memory of the Field System installed at 49 // DefaultPath, using the shared memory version specified. Returns an error if 50 // the version is not supported or the system call fails. 51 func AttachVersion(version string) (fs FieldSystem, err error) { 52 return AttachVersionPath(version, DefaultPath) 53 } 54 55 // AttachVersionPath attaches to the shared memory of the Field System installed at 56 // path, using the shared memory version specified. Returns an error if the 57 // version is not supported or the system call fails. 58 func AttachVersionPath(version, path string) (fs FieldSystem, err error) { 59 creator, ok := versions.Creators[version] 60 if !ok { 61 return nil, fmt.Errorf("load version %s: %w", version, ErrVersionNotSupported) 62 } 63 fs = creator() 64 err = fs.Attach() 65 if err != nil { 66 return nil, fmt.Errorf("attaching shared memory: %w", err) 67 } 68 return fs, nil 69 } 70 71 // SupportedVersions list the versions of the Field System supported 72 func SupportedVersions() []string { 73 v := make([]string, 0, len(versions.Creators)) 74 for k := range versions.Creators { 75 v = append(v, k) 76 } 77 return v 78 } 79 80 // InstalledVersion attempts to detect the installed version of the Field System by 81 // checking: 82 // - if path is a git repository, then using the tag. 83 // - if path is a symlink to /usr2/fs-(version) and using (version) 84 // - if path/Makefile contains the variables VERSION SUBLEVEL PATCHLEVEL 85 func InstalledVersion(path string) (string, error) { 86 version, errgit := InstalledVersionFromGit(path) 87 if errgit == nil { 88 return version, nil 89 } 90 if os.IsNotExist(errgit) { 91 return "", errgit 92 } 93 94 version, errpath := InstalledVersionFromPath(path) 95 if errpath == nil { 96 return version, nil 97 } 98 version, errmake := InstalledVersionFromMakefile(path) 99 if errmake == nil { 100 return version, nil 101 } 102 103 return "", 104 fmt.Errorf("git method: %v, path method: %v, makefile method: %v", 105 errgit, errpath, errmake) 106 } 107 108 var ErrNotGitDir = errors.New("not a git directory") 109 110 // InstalledVersionFromGit attemps to detect the FS version installed in path 111 // by using the git tags. This requires git to be in the path. 112 func InstalledVersionFromGit(path string) (string, error) { 113 var out bytes.Buffer 114 115 if _, err := os.Stat(path); err != nil { 116 return "", err 117 } 118 119 if _, err := os.Stat(path + "/.git"); os.IsNotExist(err) { 120 return "", ErrNotGitDir 121 } 122 123 gitargs := []string{ 124 fmt.Sprintf("--git-dir=%s/.git", path), 125 "describe", 126 "--always", 127 "--tags", 128 } 129 130 cmd := exec.Command("git", gitargs...) 131 cmd.Stdout = &out 132 133 if err := cmd.Run(); err != nil { 134 return "", fmt.Errorf("calling git: %w", err) 135 } 136 137 return strings.TrimSpace(out.String()), nil 138 } 139 140 var pathRegex = regexp.MustCompile(`fs-(\d+\.\d+\.\d+)$`) 141 var ErrPathMatch = errors.New("not a git directory") 142 143 // InstalledVersionFromPath attemps to detect the version of the installed 144 // Field System by examining the sympolic link "path" 145 func InstalledVersionFromPath(path string) (string, error) { 146 name, err := os.Readlink(path) 147 if err != nil { 148 return "", err 149 } 150 m := pathRegex.FindStringSubmatch(filepath.Base(name)) 151 if m == nil || len(m) == 0 { 152 return "", ErrPathMatch 153 } 154 155 return m[1], nil 156 } 157 158 // InstalledVersionFromMakefile attempts to detect the installed version of the 159 // Field System by parsing the Makefile in "/usr2/fs" directory. 160 func InstalledVersionFromMakefile(path string) (string, error) { 161 r, err := regexp.Compile(`^(\w+)\s*=\s*(\w+)$`) 162 if err != nil { 163 return "", err 164 } 165 166 vars := make(map[string]int) 167 168 f, err := os.Open(path + "/Makefile") 169 if err != nil { 170 return "", err 171 } 172 173 scanner := bufio.NewScanner(f) 174 175 for scanner.Scan() { 176 line := scanner.Text() 177 m := r.FindStringSubmatch(line) 178 if m == nil { 179 continue 180 } 181 i, err := strconv.Atoi(m[2]) 182 if err != nil { 183 continue 184 } 185 vars[m[1]] = i 186 } 187 // TODO: check if these are digits 188 return fmt.Sprintf("%d.%d.%d", vars["VERSION"], vars["SUBLEVEL"], vars["PATCHLEVEL"]), nil 189 }