github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/snapdtool/cmdutil.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019-2020 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 20 package snapdtool 21 22 import ( 23 "bufio" 24 "bytes" 25 "debug/elf" 26 "fmt" 27 "io/ioutil" 28 "os" 29 "os/exec" 30 "path/filepath" 31 "strings" 32 33 "github.com/snapcore/snapd/dirs" 34 "github.com/snapcore/snapd/osutil" 35 ) 36 37 func elfInterp(cmd string) (string, error) { 38 el, err := elf.Open(cmd) 39 if err != nil { 40 return "", err 41 } 42 defer el.Close() 43 44 for _, prog := range el.Progs { 45 if prog.Type == elf.PT_INTERP { 46 r := prog.Open() 47 interp, err := ioutil.ReadAll(r) 48 if err != nil { 49 return "", nil 50 } 51 52 return string(bytes.Trim(interp, "\x00")), nil 53 } 54 } 55 56 return "", fmt.Errorf("cannot find PT_INTERP header") 57 } 58 59 func parseLdSoConf(root string, confPath string) []string { 60 f, err := os.Open(filepath.Join(root, confPath)) 61 if err != nil { 62 return nil 63 } 64 defer f.Close() 65 66 var out []string 67 scanner := bufio.NewScanner(f) 68 for scanner.Scan() { 69 line := scanner.Text() 70 switch { 71 case strings.HasPrefix(line, "#"): 72 // nothing 73 case strings.TrimSpace(line) == "": 74 // nothing 75 case strings.HasPrefix(line, "include "): 76 l := strings.SplitN(line, "include ", 2) 77 files, err := filepath.Glob(filepath.Join(root, l[1])) 78 if err != nil { 79 return nil 80 } 81 for _, f := range files { 82 out = append(out, parseLdSoConf(root, f[len(root):])...) 83 } 84 default: 85 out = append(out, filepath.Join(root, line)) 86 } 87 88 } 89 if err := scanner.Err(); err != nil { 90 return nil 91 } 92 93 return out 94 } 95 96 // CommandFromSystemSnap runs a command from the snapd/core snap 97 // using the proper interpreter and library paths. 98 // 99 // At the moment it can only run ELF files, expects a standard ld.so 100 // interpreter, and can't handle RPATH. 101 func CommandFromSystemSnap(name string, cmdArgs ...string) (*exec.Cmd, error) { 102 from := "snapd" 103 root := filepath.Join(dirs.SnapMountDir, "/snapd/current") 104 if !osutil.FileExists(root) { 105 from = "core" 106 root = filepath.Join(dirs.SnapMountDir, "/core/current") 107 } 108 109 cmdPath := filepath.Join(root, name) 110 interp, err := elfInterp(cmdPath) 111 if err != nil { 112 return nil, err 113 } 114 coreLdSo := filepath.Join(root, interp) 115 // we cannot use EvalSymlink here because we need to resolve 116 // relative and an absolute symlinks differently. A absolute 117 // symlink is relative to root of the snapd/core snap. 118 seen := map[string]bool{} 119 for osutil.IsSymlink(coreLdSo) { 120 link, err := os.Readlink(coreLdSo) 121 if err != nil { 122 return nil, err 123 } 124 if filepath.IsAbs(link) { 125 coreLdSo = filepath.Join(root, link) 126 } else { 127 coreLdSo = filepath.Join(filepath.Dir(coreLdSo), link) 128 } 129 if seen[coreLdSo] { 130 return nil, fmt.Errorf("cannot run command from %s: symlink cycle found", from) 131 } 132 seen[coreLdSo] = true 133 } 134 135 ldLibraryPathForCore := parseLdSoConf(root, "/etc/ld.so.conf") 136 137 ldSoArgs := []string{"--library-path", strings.Join(ldLibraryPathForCore, ":"), cmdPath} 138 allArgs := append(ldSoArgs, cmdArgs...) 139 return exec.Command(coreLdSo, allArgs...), nil 140 }