@@ -0,0 +1,123 @@
1+// Take a Prybar config and exec the SQLite CLI with it.
2+//
3+// Note: the prompt config (ps1 and ps2) must be SQL strings, i.e. single quotation
4+// marks must be properly escaped.
5+package main
6+
7+import (
8+ "fmt"
9+ "io/ioutil"
10+ "os"
11+ "os/exec"
12+ "path/filepath"
13+ "syscall"
14+
15+ "github.com/replit/prybar/utils"
16+)
17+
18+var Instance = &SQLite{}
19+
20+type SQLite struct{}
21+
22+// writeConfig writes the provided slice of strings out to temporary file and
23+// returns its pathname.
24+func writeConfig(lines []string) string {
25+ f, err := ioutil.TempFile("", "sqlite-config")
26+ if err != nil {
27+ panic(err)
28+ }
29+ defer f.Close()
30+
31+ for _, line := range lines {
32+ f.WriteString(line)
33+ }
34+
35+ return f.Name()
36+}
37+
38+// preloadQuietLib adds our LD_PRELOAD lib to environment and enables it
39+func preloadQuietLib(env []string) []string {
40+ execPath, err := os.Executable()
41+ if err != nil {
42+ panic(err)
43+ }
44+ runDir := filepath.Dir(execPath)
45+ libPath := filepath.Join(runDir, "prybar_assets", "sqlite", "patch.so")
46+ return append(env, []string{"LD_PRELOAD=" + libPath, "PRYBAR_QUIET=1"}...)
47+}
48+
49+func eval(sqlite string, config *utils.Config) {
50+ args := []string{"sqlite3"}
51+ env := os.Environ()
52+
53+ if len(config.Code) > 0 {
54+ // We have to execute provided code without showing output.
55+ // So, let's have sqlite run a command that will output to /dev/null.
56+ confLines := []string{".output /dev/null\n"}
57+ args = append(args, []string{"-init", writeConfig(confLines), ":memory:", config.Code}...)
58+
59+ // add LD_PRELOAD lib to environment to suppress initialization output
60+ env = preloadQuietLib(env)
61+ } else {
62+ // we can just run the code without a config file since we want its output
63+ args = append(args, []string{":memory:", config.Exp}...)
64+ }
65+
66+ err := syscall.Exec(sqlite, args, env)
67+ if err != nil {
68+ panic(err)
69+ }
70+}
71+
72+func interactive(sqlite string, config *utils.Config) {
73+ // main and continuation prompts
74+ confLines := []string{
75+ fmt.Sprintf(".prompt '%s' '%s'\n", config.Ps1, config.Ps2),
76+ }
77+
78+ // execute file, if specified
79+ if len(config.Args) == 1 {
80+ fileToRun := config.Args[0]
81+ confLines = append(confLines, fmt.Sprintf(".read %s\n", fileToRun))
82+ }
83+
84+ args := []string{"sqlite3", "-init", writeConfig(confLines)}
85+
86+ env := os.Environ()
87+ if config.Quiet {
88+ env = preloadQuietLib(env)
89+ }
90+
91+ err := syscall.Exec(sqlite, args, env)
92+ if err != nil {
93+ panic(err)
94+ }
95+}
96+
97+func Execute(config *utils.Config) {
98+ // sanity check
99+ if len(config.Args) > 1 {
100+ fmt.Fprint(os.Stderr, "too many arguments\n")
101+ os.Exit(1)
102+ }
103+
104+ if config.OurInteractive {
105+ fmt.Fprint(os.Stderr, "not supported\n")
106+ os.Exit(1)
107+ }
108+
109+ // make sure we have the sqlite CLI
110+ sqlite, err := exec.LookPath("sqlite3")
111+ if err != nil {
112+ panic(err)
113+ }
114+
115+ if len(config.Code) > 0 || len(config.Exp) > 0 {
116+ // we're executing provided code
117+ eval(sqlite, config)
118+ } else {
119+ // running sqlite CLI interactively
120+ interactive(sqlite, config)
121+ }
122+
123+}