github.com/rigado/snapd@v2.42.5-go-mod+incompatible/cmd/cmdutil/cmdutil.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 package cmdutil 20 21 import ( 22 "bufio" 23 "bytes" 24 "debug/elf" 25 "fmt" 26 "io/ioutil" 27 "os" 28 "os/exec" 29 "path/filepath" 30 "strings" 31 32 "github.com/snapcore/snapd/dirs" 33 "github.com/snapcore/snapd/osutil" 34 ) 35 36 func elfInterp(cmd string) (string, error) { 37 el, err := elf.Open(cmd) 38 if err != nil { 39 return "", err 40 } 41 defer el.Close() 42 43 for _, prog := range el.Progs { 44 if prog.Type == elf.PT_INTERP { 45 r := prog.Open() 46 interp, err := ioutil.ReadAll(r) 47 if err != nil { 48 return "", nil 49 } 50 51 return string(bytes.Trim(interp, "\x00")), nil 52 } 53 } 54 55 return "", fmt.Errorf("cannot find PT_INTERP header") 56 } 57 58 func parseLdSoConf(root string, confPath string) []string { 59 f, err := os.Open(filepath.Join(root, confPath)) 60 if err != nil { 61 return nil 62 } 63 defer f.Close() 64 65 var out []string 66 scanner := bufio.NewScanner(f) 67 for scanner.Scan() { 68 line := scanner.Text() 69 switch { 70 case strings.HasPrefix(line, "#"): 71 // nothing 72 case strings.TrimSpace(line) == "": 73 // nothing 74 case strings.HasPrefix(line, "include "): 75 l := strings.SplitN(line, "include ", 2) 76 files, err := filepath.Glob(filepath.Join(root, l[1])) 77 if err != nil { 78 return nil 79 } 80 for _, f := range files { 81 out = append(out, parseLdSoConf(root, f[len(root):])...) 82 } 83 default: 84 out = append(out, filepath.Join(root, line)) 85 } 86 87 } 88 if err := scanner.Err(); err != nil { 89 return nil 90 } 91 92 return out 93 } 94 95 // CommandFromSystemSnap runs a command from the snapd/core snap 96 // using the proper interpreter and library paths. 97 // 98 // At the moment it can only run ELF files, expects a standard ld.so 99 // interpreter, and can't handle RPATH. 100 func CommandFromSystemSnap(name string, cmdArgs ...string) (*exec.Cmd, error) { 101 from := "snapd" 102 root := filepath.Join(dirs.SnapMountDir, "/snapd/current") 103 if !osutil.FileExists(root) { 104 from = "core" 105 root = filepath.Join(dirs.SnapMountDir, "/core/current") 106 } 107 108 cmdPath := filepath.Join(root, name) 109 interp, err := elfInterp(cmdPath) 110 if err != nil { 111 return nil, err 112 } 113 coreLdSo := filepath.Join(root, interp) 114 // we cannot use EvalSymlink here because we need to resolve 115 // relative and an absolute symlinks differently. A absolute 116 // symlink is relative to root of the snapd/core snap. 117 seen := map[string]bool{} 118 for osutil.IsSymlink(coreLdSo) { 119 link, err := os.Readlink(coreLdSo) 120 if err != nil { 121 return nil, err 122 } 123 if filepath.IsAbs(link) { 124 coreLdSo = filepath.Join(root, link) 125 } else { 126 coreLdSo = filepath.Join(filepath.Dir(coreLdSo), link) 127 } 128 if seen[coreLdSo] { 129 return nil, fmt.Errorf("cannot run command from %s: symlink cycle found", from) 130 } 131 seen[coreLdSo] = true 132 } 133 134 ldLibraryPathForCore := parseLdSoConf(root, "/etc/ld.so.conf") 135 136 ldSoArgs := []string{"--library-path", strings.Join(ldLibraryPathForCore, ":"), cmdPath} 137 allArgs := append(ldSoArgs, cmdArgs...) 138 return exec.Command(coreLdSo, allArgs...), nil 139 }