2021-10-26 03:39:02 +03:00
//go:build !cgo && upgrade && ignore
// +build !cgo,upgrade,ignore
2018-05-30 20:49:32 +03:00
package main
import (
"archive/zip"
"bufio"
"bytes"
2023-12-13 15:39:26 +03:00
"encoding/csv"
2020-04-14 20:04:46 +03:00
"encoding/hex"
"errors"
2020-06-30 19:55:45 +03:00
"flag"
2018-05-30 20:49:32 +03:00
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
2023-12-13 15:39:26 +03:00
"net/url"
2018-05-30 20:49:32 +03:00
"os"
2020-06-30 19:55:45 +03:00
"os/exec"
2018-05-30 20:49:32 +03:00
"path/filepath"
"strings"
2020-09-03 00:52:19 +03:00
"golang.org/x/crypto/sha3"
2023-12-13 15:39:26 +03:00
"golang.org/x/net/html"
2018-05-30 20:49:32 +03:00
)
2020-06-30 19:55:45 +03:00
var (
cFlags = "-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1"
cleanup = true
2023-12-13 15:39:26 +03:00
errNotFound = errors . New ( "records not found" )
)
const (
sqliteAddress = "https://www.sqlite.org"
downloadPage = "download.html"
sqliteDownloadDataTag = "Download product data for scripts to read"
urlColumnName = "RELATIVE-URL"
hashColumnName = "SHA3-HASH"
2020-06-30 19:55:45 +03:00
)
2020-04-14 20:04:46 +03:00
func main ( ) {
2020-06-30 19:55:45 +03:00
flag . StringVar ( & shellPath , "shell" , shellPath , "path to shell executable" )
flag . StringVar ( & makePath , "make" , makePath , "path to make executable" )
flag . StringVar ( & cFlags , "cflags" , cFlags , "sqlite CFLAGS" )
flag . BoolVar ( & cleanup , "cleanup" , cleanup , "cleanup source" )
flag . Parse ( )
2020-04-14 20:04:46 +03:00
err := func ( ) error {
fmt . Println ( "Go-SQLite3 Upgrade Tool" )
wd , err := os . Getwd ( )
if err != nil {
return err
}
if filepath . Base ( wd ) != "upgrade" {
return fmt . Errorf ( "Current directory is %q but should run in upgrade directory" , wd )
}
// Download Source
source , hash , err := download ( "sqlite-src-" )
if err != nil {
return fmt . Errorf ( "failed to download: sqlite-src; %v" , err )
}
fmt . Printf ( "Download successful and verified hash %x\n" , hash )
// Extract Source
baseDir , err := extractZip ( source )
2020-06-30 19:55:45 +03:00
if cleanup && baseDir != "" && ! filepath . IsAbs ( baseDir ) {
2020-04-14 20:04:46 +03:00
defer func ( ) {
fmt . Println ( "Cleaning up source: deleting" , baseDir )
os . RemoveAll ( baseDir )
} ( )
}
if err != nil {
return fmt . Errorf ( "failed to extract source: %v" , err )
}
fmt . Println ( "Extracted sqlite source to" , baseDir )
2020-06-30 19:55:45 +03:00
// Build amalgamation files
fmt . Printf ( "Starting to generate amalgamation with CFLAGS: %s\n" , cFlags )
if err := buildAmalgamation ( baseDir , cFlags ) ; err != nil {
2020-04-14 20:04:46 +03:00
return fmt . Errorf ( "failed to build amalgamation: %v" , err )
}
fmt . Println ( "SQLite3 amalgamation built" )
// Patch bindings
patchSource ( baseDir , "sqlite3.c" , "../sqlite3-binding.c" , "ext/userauth/userauth.c" )
patchSource ( baseDir , "sqlite3.h" , "../sqlite3-binding.h" , "ext/userauth/sqlite3userauth.h" )
patchSource ( baseDir , "sqlite3ext.h" , "../sqlite3ext.h" )
fmt . Println ( "Done patching amalgamation" )
return nil
} ( )
if err != nil {
2020-06-30 19:55:45 +03:00
log . Fatalln ( "Returned with error:" , err )
2020-04-14 20:04:46 +03:00
}
}
2023-12-13 15:39:26 +03:00
func findAddress ( n * html . Node , prefix string ) ( string , string , error ) {
if n . Type == html . CommentNode && strings . Contains ( n . Data , sqliteDownloadDataTag ) {
data := strings . TrimSpace ( n . Data [ strings . Index ( n . Data , "\n" ) + 1 : ] )
r := csv . NewReader ( strings . NewReader ( data ) )
records , err := r . ReadAll ( )
if err != nil {
return "" , "" , err
}
for i , r := range records {
if len ( r ) != 5 {
return "" , "" , fmt . Errorf ( "expected record of length 5, got: %v" , r )
}
if i == 0 && ( r [ 2 ] != urlColumnName || r [ 4 ] != hashColumnName ) {
return "" , "" , fmt . Errorf ( "unexpected columns: %v" , r )
}
if strings . Contains ( r [ 2 ] , prefix ) {
return r [ 2 ] , r [ 4 ] , nil
}
}
}
mainErr := errNotFound
for c := n . FirstChild ; c != nil ; c = c . NextSibling {
path , hash , err := findAddress ( c , prefix )
if err == nil {
return path , hash , nil
}
if err != errNotFound {
mainErr = err
}
}
return "" , "" , mainErr
}
2020-04-14 20:04:46 +03:00
func download ( prefix string ) ( content , hash [ ] byte , err error ) {
2023-12-13 15:39:26 +03:00
u , err := url . Parse ( sqliteAddress )
if err != nil {
return nil , nil , err
}
2018-05-30 20:49:32 +03:00
2023-12-13 15:39:26 +03:00
dwnldPage , err := http . Get ( u . JoinPath ( downloadPage ) . String ( ) )
if err != nil {
return nil , nil , err
}
defer dwnldPage . Body . Close ( )
node , err := html . Parse ( dwnldPage . Body )
2018-05-30 20:49:32 +03:00
if err != nil {
2020-04-14 20:04:46 +03:00
return nil , nil , err
2018-05-30 20:49:32 +03:00
}
2023-12-13 15:39:26 +03:00
relPath , hashString , err := findAddress ( node , prefix )
if err != nil {
return nil , nil , fmt . Errorf ( "failed to find download info in the document: %w" , err )
}
2018-05-30 20:49:32 +03:00
2020-04-14 20:04:46 +03:00
targetHash , err := hex . DecodeString ( hashString )
2020-09-03 00:52:19 +03:00
if err != nil || len ( targetHash ) != 32 {
2023-12-13 15:39:26 +03:00
return nil , nil , fmt . Errorf ( "unable to find valid sha3-256 hash on download page: %q" , hashString )
2020-04-14 20:04:46 +03:00
}
2023-12-13 15:39:26 +03:00
if relPath == "" {
return nil , nil , fmt . Errorf ( "unable to find prefix '%s' on download page" , prefix )
2018-05-30 20:49:32 +03:00
}
2023-12-13 15:39:26 +03:00
src := u . JoinPath ( relPath ) . String ( )
fmt . Printf ( "Downloading %v\n" , src )
resp , err := http . Get ( src )
2018-05-30 20:49:32 +03:00
if err != nil {
2020-04-14 20:04:46 +03:00
return nil , nil , err
2018-05-30 20:49:32 +03:00
}
2023-12-13 15:39:26 +03:00
defer resp . Body . Close ( )
2018-05-30 20:49:32 +03:00
// Ready Body Content
2020-09-03 00:52:19 +03:00
shasum := sha3 . New256 ( )
2020-04-14 20:04:46 +03:00
content , err = ioutil . ReadAll ( io . TeeReader ( resp . Body , shasum ) )
2018-05-30 20:49:32 +03:00
if err != nil {
2020-04-14 20:04:46 +03:00
return nil , nil , err
2018-05-30 20:49:32 +03:00
}
2020-04-14 20:04:46 +03:00
computedHash := shasum . Sum ( nil )
if ! bytes . Equal ( targetHash , computedHash ) {
2023-12-13 15:39:26 +03:00
return nil , nil , fmt . Errorf ( "invalid hash of file downloaded from %q: got %x instead of %x" , src , computedHash , targetHash )
2018-05-30 20:49:32 +03:00
}
2020-04-14 20:04:46 +03:00
return content , computedHash , nil
2018-05-30 20:49:32 +03:00
}
2020-04-14 20:04:46 +03:00
func extractZip ( data [ ] byte ) ( string , error ) {
zr , err := zip . NewReader ( bytes . NewReader ( data ) , int64 ( len ( data ) ) )
2021-10-26 03:39:02 +03:00
if err != nil {
2020-04-14 20:04:46 +03:00
return "" , err
2021-10-26 03:39:02 +03:00
}
2020-04-14 20:04:46 +03:00
if len ( zr . File ) == 0 {
return "" , errors . New ( "no files in zip archive" )
2018-05-30 20:49:32 +03:00
}
2020-04-14 20:04:46 +03:00
if ! zr . File [ 0 ] . Mode ( ) . IsDir ( ) {
return "" , errors . New ( "expecting base directory at the top of zip archive" )
2018-05-30 20:49:32 +03:00
}
2020-04-14 20:04:46 +03:00
baseDir := zr . File [ 0 ] . Name
2018-05-30 20:49:32 +03:00
2020-04-14 20:04:46 +03:00
for _ , zf := range zr . File {
if ! strings . HasPrefix ( zf . Name , baseDir ) {
return baseDir , fmt . Errorf ( "file %q in zip archive not in base directory %q" , zf . Name , baseDir )
}
2018-05-30 20:49:32 +03:00
2020-04-14 20:04:46 +03:00
if zf . Mode ( ) . IsDir ( ) {
if err := os . Mkdir ( zf . Name , zf . Mode ( ) ) ; err != nil {
return baseDir , err
}
2018-05-30 20:49:32 +03:00
continue
}
2020-04-14 20:04:46 +03:00
f , err := os . OpenFile ( zf . Name , os . O_WRONLY | os . O_CREATE | os . O_EXCL , zf . Mode ( ) )
2018-05-30 20:49:32 +03:00
if err != nil {
2020-04-14 20:04:46 +03:00
return baseDir , err
}
if zf . UncompressedSize == 0 {
continue
2018-05-30 20:49:32 +03:00
}
2020-04-14 20:04:46 +03:00
2018-05-30 20:49:32 +03:00
zr , err := zf . Open ( )
if err != nil {
2020-04-14 20:04:46 +03:00
return baseDir , err
2018-05-30 20:49:32 +03:00
}
2020-04-14 20:04:46 +03:00
_ , err = io . Copy ( f , zr )
2018-05-30 20:49:32 +03:00
if err != nil {
2020-04-14 20:04:46 +03:00
return baseDir , err
2018-05-30 20:49:32 +03:00
}
2020-04-14 20:04:46 +03:00
if err := zr . Close ( ) ; err != nil {
return baseDir , err
2018-05-30 20:49:32 +03:00
}
2020-04-14 20:04:46 +03:00
if err := f . Close ( ) ; err != nil {
return baseDir , err
2018-05-30 20:49:32 +03:00
}
2020-04-14 20:04:46 +03:00
}
return baseDir , nil
}
func patchSource ( baseDir , src , dst string , extensions ... string ) error {
srcFile , err := os . Open ( filepath . Join ( baseDir , src ) )
if err != nil {
return err
}
defer srcFile . Close ( )
dstFile , err := os . Create ( dst )
if err != nil {
return err
}
defer dstFile . Close ( )
_ , err = io . WriteString ( dstFile , "#ifndef USE_LIBSQLITE3\n" )
if err != nil {
return err
}
scanner := bufio . NewScanner ( srcFile )
for scanner . Scan ( ) {
text := scanner . Text ( )
if text == ` #include "sqlite3.h" ` {
text = ` #include "sqlite3-binding.h" `
2018-05-30 20:49:32 +03:00
}
2020-04-14 20:04:46 +03:00
_ , err = fmt . Fprintln ( dstFile , text )
2018-05-30 20:49:32 +03:00
if err != nil {
2020-04-14 20:04:46 +03:00
break
2018-05-30 20:49:32 +03:00
}
2020-04-14 20:04:46 +03:00
}
err = scanner . Err ( )
if err != nil {
return err
}
_ , err = io . WriteString ( dstFile , "#else // USE_LIBSQLITE3\n// If users really want to link against the system sqlite3 we\n// need to make this file a noop.\n#endif\n" )
if err != nil {
return err
}
2018-05-30 20:49:32 +03:00
2020-04-14 20:04:46 +03:00
for _ , ext := range extensions {
ext = filepath . FromSlash ( ext )
fmt . Printf ( "Merging: %s into %s\n" , ext , dst )
extFile , err := os . Open ( filepath . Join ( baseDir , ext ) )
2018-05-30 20:49:32 +03:00
if err != nil {
2020-04-14 20:04:46 +03:00
return err
}
_ , err = io . Copy ( dstFile , extFile )
extFile . Close ( )
if err != nil {
return err
2018-05-30 20:49:32 +03:00
}
}
2020-04-14 20:04:46 +03:00
if err := dstFile . Close ( ) ; err != nil {
return err
2018-05-30 20:49:32 +03:00
}
2020-04-14 20:04:46 +03:00
fmt . Printf ( "Patched: %s -> %s\n" , src , dst )
return nil
2018-05-30 20:49:32 +03:00
}
2020-06-30 19:55:45 +03:00
func buildAmalgamation ( baseDir , buildFlags string ) error {
configureScript := "./configure"
if cFlags != "" {
configureScript += fmt . Sprintf ( " CFLAGS=%q" , cFlags )
}
cmd := exec . Command ( shellPath , "-c" , configureScript )
cmd . Dir = baseDir
out , err := cmd . CombinedOutput ( )
if err != nil {
return fmt . Errorf ( "configure failed: %v\n\n%s" , err , out )
}
fmt . Println ( "Ran configure successfully" )
cmd = exec . Command ( makePath , "sqlite3.c" )
cmd . Dir = baseDir
out , err = cmd . CombinedOutput ( )
if err != nil {
return fmt . Errorf ( "make failed: %v\n\n%s" , err , out )
}
fmt . Println ( "Ran make successfully" )
return nil
}