Work around sqlite's incorrect handling of umask when creating databases.
authorJoey Hess <joeyh@joeyh.name>
Mon, 13 Feb 2017 21:30:28 +0000 (17:30 -0400)
committerJoey Hess <joeyh@joeyh.name>
Mon, 13 Feb 2017 21:39:16 +0000 (17:39 -0400)
Refactored some common code into initDb.

This only deals with the problem when creating new databases. If a repo
got bad permissions into it, it's up to the user to deal with it.

This commit was sponsored by Ole-Morten Duesund on Patreon.

CHANGELOG
Database/Fsck.hs
Database/Handle.hs
Database/Init.hs [new file with mode: 0644]
Database/Keys.hs
Database/Queue.hs
Utility/FileMode.hs
doc/bugs/Aborts_with_SQLite_error_when_dropping_contents.mdwn
doc/bugs/Aborts_with_SQLite_error_when_dropping_contents/comment_3_88a09558f2da0a5733aa3dd042f9d59b._comment [new file with mode: 0644]
git-annex.cabal

index b359ff8488fbb1a045ada9a7703e57cf5fec2988..19826d6553a13b55d76297eeafe8c8c6be3a5015 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -52,6 +52,8 @@ git-annex (6.20170102) UNRELEASED; urgency=medium
   * Improve pid locking code to work on filesystems that don't support hard
     links.
   * S3: Fix check of uuid file stored in bucket, which was not working.
+  * Work around sqlite's incorrect handling of umask when creating
+    databases.
 
  -- Joey Hess <id@joeyh.name>  Fri, 06 Jan 2017 15:22:06 -0400
 
index 702b529257a767b6b5df09ae92516d84ca593ebe..9affeac8563f498fb574fff19cec9e2251f7e175 100644 (file)
@@ -22,11 +22,10 @@ module Database.Fsck (
 
 import Database.Types
 import qualified Database.Queue as H
+import Database.Init
 import Annex.Locations
-import Utility.PosixFiles
 import Utility.Exception
 import Annex.Common
-import Annex.Perms
 import Annex.LockFile
 
 import Database.Persist.TH
@@ -61,17 +60,8 @@ openDb u = do
        dbdir <- fromRepo (gitAnnexFsckDbDir u)
        let db = dbdir </> "db"
        unlessM (liftIO $ doesFileExist db) $ do
-               let tmpdbdir = dbdir ++ ".tmp"
-               let tmpdb = tmpdbdir </> "db"
-               liftIO $ do
-                       createDirectoryIfMissing True tmpdbdir
-                       H.initDb tmpdb $ void $
-                               runMigrationSilent migrateFsck
-               setAnnexDirPerm tmpdbdir
-               setAnnexFilePerm tmpdb
-               liftIO $ do
-                       void $ tryIO $ removeDirectoryRecursive dbdir
-                       rename tmpdbdir dbdir
+               initDb db $ void $
+                       runMigrationSilent migrateFsck
        lockFileCached =<< fromRepo (gitAnnexFsckDbLock u)
        h <- liftIO $ H.openDbQueue db "fscked"
        return $ FsckHandle h u
index d84ce5b6209655967cf8e0061e1c7b2e470b5671..7827be7497b11bce4ab1296e09e9621167e5790e 100644 (file)
@@ -9,7 +9,6 @@
 
 module Database.Handle (
        DbHandle,
-       initDb,
        openDb,
        TableName,
        queryDb,
@@ -38,26 +37,6 @@ import System.IO
  - the database. It has a MVar which Jobs are submitted to. -}
 data DbHandle = DbHandle (Async ()) (MVar Job)
 
-{- Ensures that the database is initialized. Pass the migration action for
- - the database.
- -
- - The database is initialized using WAL mode, to prevent readers
- - from blocking writers, and prevent a writer from blocking readers.
- -}
-initDb :: FilePath -> SqlPersistM () -> IO ()
-initDb f migration = do
-       let db = T.pack f
-       enableWAL db
-       runSqlite db migration
-
-enableWAL :: T.Text -> IO ()
-enableWAL db = do
-       conn <- Sqlite.open db
-       stmt <- Sqlite.prepare conn (T.pack "PRAGMA journal_mode=WAL;")
-       void $ Sqlite.step stmt
-       void $ Sqlite.finalize stmt
-       Sqlite.close conn
-
 {- Name of a table that should exist once the database is initialized. -}
 type TableName = String
 
diff --git a/Database/Init.hs b/Database/Init.hs
new file mode 100644 (file)
index 0000000..d7a7f68
--- /dev/null
@@ -0,0 +1,55 @@
+{- Persistent sqlite database initialization
+ -
+ - Copyright 2015-2017 Joey Hess <id@joeyh.name>
+ -
+ - Licensed under the GNU GPL version 3 or higher.
+ -}
+
+module Database.Init where
+
+import Annex.Common
+import Annex.Perms
+import Utility.FileMode
+
+import Database.Persist.Sqlite
+import qualified Database.Sqlite as Sqlite
+import Control.Monad.IO.Class (liftIO)
+import qualified Data.Text as T
+
+{- Ensures that the database is freshly initialized. Deletes any
+ - existing database. Pass the migration action for the database.
+ -
+ - The database is initialized using WAL mode, to prevent readers
+ - from blocking writers, and prevent a writer from blocking readers.
+ -
+ - The permissions of the database are set based on the
+ - core.sharedRepository setting. Setting these permissions on the main db
+ - file causes Sqlite to always use the same permissions for additional
+ - files it writes later on
+ -}
+initDb :: FilePath -> SqlPersistM () -> Annex ()
+initDb db migration = do
+       let dbdir = takeDirectory db
+       let tmpdbdir = dbdir ++ ".tmp"
+       let tmpdb = tmpdbdir </> "db"
+       liftIO $ do
+               createDirectoryIfMissing True tmpdbdir
+               let tdb = T.pack tmpdb
+               enableWAL tdb
+               runSqlite tdb migration
+       setAnnexDirPerm tmpdbdir
+       -- Work around sqlite bug that prevents it from honoring
+       -- less restrictive umasks.
+       liftIO $ setFileMode tmpdb =<< defaultFileMode
+       setAnnexFilePerm tmpdb
+       liftIO $ do
+               void $ tryIO $ removeDirectoryRecursive dbdir
+               rename tmpdbdir dbdir
+
+enableWAL :: T.Text -> IO ()
+enableWAL db = do
+       conn <- Sqlite.open db
+       stmt <- Sqlite.prepare conn (T.pack "PRAGMA journal_mode=WAL;")
+       void $ Sqlite.step stmt
+       void $ Sqlite.finalize stmt
+       Sqlite.close conn
index 0f2f34930e111acefe0096dd6085d53ad83328ef..b9440ac1adc5ee9320cb560fd7b75533a94a2426 100644 (file)
@@ -25,11 +25,11 @@ import qualified Database.Keys.SQL as SQL
 import Database.Types
 import Database.Keys.Handle
 import qualified Database.Queue as H
+import Database.Init
 import Annex.Locations
 import Annex.Common hiding (delete)
 import Annex.Version (versionUsesKeysDatabase)
 import qualified Annex
-import Annex.Perms
 import Annex.LockFile
 import Utility.InodeCache
 import Annex.InodeSentinal
@@ -120,11 +120,7 @@ openDb createdb _ = catchPermissionDenied permerr $ withExclusiveLock gitAnnexKe
        case (dbexists, createdb) of
                (True, _) -> open db
                (False, True) -> do
-                       liftIO $ do
-                               createDirectoryIfMissing True dbdir
-                               H.initDb db SQL.createTables
-                       setAnnexDirPerm dbdir
-                       setAnnexFilePerm db
+                       initDb db SQL.createTables
                        open db
                (False, False) -> return DbUnavailable
   where
index c4186b8c80792c63b3eee3c3f09b9927038b8853..143871079b51ab6e7a0981f6e07d3070e6b586ac 100644 (file)
@@ -9,7 +9,6 @@
 
 module Database.Queue (
        DbQueue,
-       initDb,
        openDbQueue,
        queryDbQueue,
        closeDbQueue,
index bb3780c6e2ad323b54035a5355eedf63224c5730..fe9cbf56a3d82b7eb81281525ef2ac6f3b0d19ff 100644 (file)
@@ -1,6 +1,6 @@
 {- File mode utilities.
  -
- - Copyright 2010-2012 Joey Hess <id@joeyh.name>
+ - Copyright 2010-2017 Joey Hess <id@joeyh.name>
  -
  - License: BSD-2-clause
  -}
@@ -130,6 +130,21 @@ withUmask umask a = bracket setup cleanup go
 withUmask _ a = a
 #endif
 
+getUmask :: IO FileMode
+#ifndef mingw32_HOST_OS
+getUmask = bracket setup cleanup return
+  where
+       setup = setFileCreationMask nullFileMode
+       cleanup = setFileCreationMask
+#else
+getUmask = return nullFileMode
+#endif
+
+defaultFileMode :: IO FileMode
+defaultFileMode = do
+       umask <- getUmask
+       return $ intersectFileModes (complement umask) stdFileMode
+
 combineModes :: [FileMode] -> FileMode
 combineModes [] = 0
 combineModes [m] = m
index 5acf5f0751534cb11a01ef38599a2c278ec306aa..de6c26fbfebc1ea126bd8d3e607ea99b4c2b50b0 100644 (file)
@@ -81,3 +81,6 @@ extra copies.
 In other words, Git-Annex and I are very happy together, and I'd like to 
 marry it. And because you are the father, I hereby respectfully ask for 
 your blessing.
+
+> [[fixed|done]] (and I suppose you have my blessing, but I'm not sure
+> that's legal yet!) --[[Joey]]
diff --git a/doc/bugs/Aborts_with_SQLite_error_when_dropping_contents/comment_3_88a09558f2da0a5733aa3dd042f9d59b._comment b/doc/bugs/Aborts_with_SQLite_error_when_dropping_contents/comment_3_88a09558f2da0a5733aa3dd042f9d59b._comment
new file mode 100644 (file)
index 0000000..30e108b
--- /dev/null
@@ -0,0 +1,53 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 3"""
+ date="2017-02-13T20:21:09Z"
+ content="""
+Thanks for following up with the cause of this.
+
+In fact, assuming you're not using a v6 git-annex repository, it doesn't
+really need to update that database at all. But since we'll be upgrading to
+v6 eventually, I need to deal with problems like this. Also,
+this same problem will also impact the database used for incremental fsck.
+
+I can reproduce this with a v5 repository; dropping a file happens to run a
+code path that updates the database. And reproducing it w/o using git-annex too:
+
+       joey@darkstar:~/tmp>mkdir empty
+       joey@darkstar:~/tmp>umask
+       0002
+       joey@darkstar:~/tmp>touch empty/file
+       joey@darkstar:~/tmp>sqlite3 empty/db
+       SQLite version 3.16.2 2017-01-06 16:32:41
+       Enter ".help" for usage hints.
+       sqlite> create table foo;
+       Error: near ";": syntax error
+       sqlite> 
+       joey@darkstar:~/tmp>ls -l empty/
+       total 0
+       -rw-r--r-- 1 joey joey 0 Feb 13 16:33 db
+       -rw-rw-r-- 1 joey joey 0 Feb 13 16:32 file
+
+Seems that sqlite uses `0644 & umask` for the db permissions, 
+which is *bad* because it doesn't allow the umask to enable the group
+write bit. That 0644 is `SQLITE_DEFAULT_FILE_PERMISSIONS`, so it can
+be changed to something saner at compile time.
+
+`http://www.sqlite.org/src/doc/trunk/src/os_unix.c` has a useful comment.
+Seems that sqlite is careful to make -wal, -journal, and -shm files
+have the exact same permissions as main database file.
+
+So, if `.git/annex/keys/*` is updated to have the desired permissions when
+the database is created, every further write to the database will keep
+using the desired permissions.
+
+Hmm, it turns out that git-annex does already set the database permissions when
+creating it, but only if core.sharedRepository is set to group or all. So
+there's a workaround; just `git config core.sharedRepository group` when
+setting up a repository that's going to be accessed by multiple users. Almost
+certianly a better idea than relying on umask anyway; people mess up umask
+settings.
+
+I'll go ahead and make it always set sane permissions when creating databases.
+I'm not going to try to fix up permissions in existing repositories though.
+"""]]
index 5bcc67315af8500405044226fc1b426921005804..37e2389a0279ec53baf09aa5f0fbcbd077dabfa8 100644 (file)
@@ -798,6 +798,7 @@ Executable git-annex
     Crypto
     Database.Fsck
     Database.Handle
+    Database.Init
     Database.Keys
     Database.Keys.Handle
     Database.Keys.SQL