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  }