88 "log"
99 "os"
1010 "os/exec"
11+ "strconv"
1112 "strings"
1213 "time"
1314
@@ -64,7 +65,7 @@ func parseTimestampFlag() (*time.Time, error) {
6465 return & timestamp , nil
6566}
6667
67- func parseDBSeedFlags (client * turso.Client , isAWS bool ) (* turso.DBSeed , error ) {
68+ func parseDBSeedFlags (client * turso.Client , isAWS bool , cipher string ) (* turso.DBSeed , error ) {
6869 if countFlags (fromDBFlag , fromDumpFlag , fromFileFlag , fromDumpURLFlag , fromCSVFlag ) > 1 {
6970 return nil , errors .New ("only one of --from prefixed flags can be used at a time" )
7071 }
@@ -85,7 +86,7 @@ func parseDBSeedFlags(client *turso.Client, isAWS bool) (*turso.DBSeed, error) {
8586 }
8687
8788 if fromFileFlag != "" {
88- return handleDBFile (client , fromFileFlag , isAWS )
89+ return handleDBFile (client , fromFileFlag , isAWS , cipher )
8990 }
9091
9192 if fromDumpFlag != "" {
@@ -97,7 +98,7 @@ func parseDBSeedFlags(client *turso.Client, isAWS bool) (*turso.DBSeed, error) {
9798 if err != nil {
9899 return nil , err
99100 }
100- return handleCSVFile (client , fromCSVFlag , csvTableNameFlag , csvSeparator )
101+ return handleCSVFile (client , fromCSVFlag , csvTableNameFlag , csvSeparator , cipher )
101102 }
102103 if fromDumpURLFlag != "" {
103104 return handleDumpURL (fromDumpURLFlag )
@@ -205,7 +206,50 @@ func checkIfDump(filename string) (bool, error) {
205206 }
206207}
207208
208- func sqliteFileIntegrityChecks (file string ) error {
209+ // getReservedBytes retrieves the current reserved bytes setting from a SQLite database
210+ func getReservedBytes (dbPath string ) (int , error ) {
211+ output , err := exec .Command ("sqlite3" , dbPath , ".filectrl reserve_bytes" ).CombinedOutput ()
212+ if err != nil {
213+ return 0 , fmt .Errorf ("failed to get reserved bytes: %w" , err )
214+ }
215+ outputStr := strings .TrimSpace (string (output ))
216+
217+ if strings .Contains (outputStr , ":" ) {
218+ parts := strings .Split (outputStr , ":" )
219+ if len (parts ) >= 2 {
220+ outputStr = strings .TrimSpace (parts [1 ])
221+ }
222+ }
223+
224+ reservedBytes , err := strconv .Atoi (outputStr )
225+ if err != nil {
226+ return 0 , fmt .Errorf ("failed to parse reserved bytes from output '%s': %w" , string (output ), err )
227+ }
228+
229+ return reservedBytes , nil
230+ }
231+
232+ // validateReservedBytes checks if the database has the required reserved bytes for the given cipher
233+ func validateReservedBytes (dbPath string , cipher string ) error {
234+ requiredBytes , ok := getRequiredReservedBytes (cipher )
235+ if ! ok {
236+ return nil
237+ }
238+
239+ currentBytes , err := getReservedBytes (dbPath )
240+ if err != nil {
241+ return err
242+ }
243+
244+ if currentBytes != requiredBytes {
245+ return fmt .Errorf ("database reserved bytes mismatch: found %d, but cipher '%s' requires %d reserved bytes.\n To fix this, run:\n \n $ sqlite3 %s\n sqlite> .filectrl reserve_bytes %d\n sqlite> VACUUM;" ,
246+ currentBytes , cipher , requiredBytes , dbPath , requiredBytes )
247+ }
248+
249+ return nil
250+ }
251+
252+ func sqliteFileIntegrityChecks (file string , cipher string ) error {
209253 if flags .Debug () {
210254 log .Printf ("Running integrity checks on database file %s" , file )
211255 }
@@ -268,11 +312,19 @@ func sqliteFileIntegrityChecks(file string) error {
268312 return fmt .Errorf ("integrity check on database failed: %w" , err )
269313 }
270314
315+ // validate reserved bytes if encryption cipher is specified
316+ if cipher != "" {
317+ if flags .Debug () {
318+ log .Printf ("Checking reserved bytes for cipher %s..." , cipher )
319+ }
320+ return validateReservedBytes (file , cipher )
321+ }
322+
271323 return nil
272324}
273325
274- func handleDBFileAWS (file string ) (* turso.DBSeed , error ) {
275- if err := sqliteFileIntegrityChecks (file ); err != nil {
326+ func handleDBFileAWS (file string , cipher string ) (* turso.DBSeed , error ) {
327+ if err := sqliteFileIntegrityChecks (file , cipher ); err != nil {
276328 return nil , err
277329 }
278330
@@ -284,7 +336,7 @@ func handleDBFileAWS(file string) (*turso.DBSeed, error) {
284336 return seed , nil
285337}
286338
287- func handleDBFile (client * turso.Client , file string , isAWS bool ) (* turso.DBSeed , error ) {
339+ func handleDBFile (client * turso.Client , file string , isAWS bool , cipher string ) (* turso.DBSeed , error ) {
288340 if err := checkFileExists (file ); err != nil {
289341 return nil , err
290342 }
@@ -293,7 +345,7 @@ func handleDBFile(client *turso.Client, file string, isAWS bool) (*turso.DBSeed,
293345 }
294346
295347 if isAWS {
296- return handleDBFileAWS (file )
348+ return handleDBFileAWS (file , cipher )
297349 }
298350
299351 if err := checkSQLiteFile (file ); err != nil {
@@ -363,7 +415,7 @@ func dumpSQLiteDatabase(database string, dump *os.File) error {
363415 return nil
364416}
365417
366- func handleCSVFile (client * turso.Client , file , csvTableName string , separator rune ) (* turso.DBSeed , error ) {
418+ func handleCSVFile (client * turso.Client , file , csvTableName string , separator rune , cipher string ) (* turso.DBSeed , error ) {
367419 if err := checkFileExists (file ); err != nil {
368420 return nil , err
369421 }
@@ -393,7 +445,7 @@ func handleCSVFile(client *turso.Client, file, csvTableName string, separator ru
393445 return nil , err
394446 }
395447
396- seed , err := handleDBFile (client , tempDB .Name (), false )
448+ seed , err := handleDBFile (client , tempDB .Name (), false , cipher )
397449 if err != nil {
398450 return nil , err
399451 }
0 commit comments