Work around sqlite's incorrect handling of umask when creating databases.

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.
This commit is contained in:
Joey Hess 2017-02-13 17:30:28 -04:00
parent d2174915c0
commit 3b22ad9f47
No known key found for this signature in database
GPG key ID: C910D9222512E3C7
10 changed files with 135 additions and 42 deletions

View file

@ -52,6 +52,8 @@ git-annex (6.20170102) UNRELEASED; urgency=medium
* Improve pid locking code to work on filesystems that don't support hard * Improve pid locking code to work on filesystems that don't support hard
links. links.
* S3: Fix check of uuid file stored in bucket, which was not working. * 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 -- Joey Hess <id@joeyh.name> Fri, 06 Jan 2017 15:22:06 -0400

View file

@ -22,11 +22,10 @@ module Database.Fsck (
import Database.Types import Database.Types
import qualified Database.Queue as H import qualified Database.Queue as H
import Database.Init
import Annex.Locations import Annex.Locations
import Utility.PosixFiles
import Utility.Exception import Utility.Exception
import Annex.Common import Annex.Common
import Annex.Perms
import Annex.LockFile import Annex.LockFile
import Database.Persist.TH import Database.Persist.TH
@ -61,17 +60,8 @@ openDb u = do
dbdir <- fromRepo (gitAnnexFsckDbDir u) dbdir <- fromRepo (gitAnnexFsckDbDir u)
let db = dbdir </> "db" let db = dbdir </> "db"
unlessM (liftIO $ doesFileExist db) $ do unlessM (liftIO $ doesFileExist db) $ do
let tmpdbdir = dbdir ++ ".tmp" initDb db $ void $
let tmpdb = tmpdbdir </> "db" runMigrationSilent migrateFsck
liftIO $ do
createDirectoryIfMissing True tmpdbdir
H.initDb tmpdb $ void $
runMigrationSilent migrateFsck
setAnnexDirPerm tmpdbdir
setAnnexFilePerm tmpdb
liftIO $ do
void $ tryIO $ removeDirectoryRecursive dbdir
rename tmpdbdir dbdir
lockFileCached =<< fromRepo (gitAnnexFsckDbLock u) lockFileCached =<< fromRepo (gitAnnexFsckDbLock u)
h <- liftIO $ H.openDbQueue db "fscked" h <- liftIO $ H.openDbQueue db "fscked"
return $ FsckHandle h u return $ FsckHandle h u

View file

@ -9,7 +9,6 @@
module Database.Handle ( module Database.Handle (
DbHandle, DbHandle,
initDb,
openDb, openDb,
TableName, TableName,
queryDb, queryDb,
@ -38,26 +37,6 @@ import System.IO
- the database. It has a MVar which Jobs are submitted to. -} - the database. It has a MVar which Jobs are submitted to. -}
data DbHandle = DbHandle (Async ()) (MVar Job) 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. -} {- Name of a table that should exist once the database is initialized. -}
type TableName = String type TableName = String

55
Database/Init.hs Normal file
View file

@ -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

View file

@ -25,11 +25,11 @@ import qualified Database.Keys.SQL as SQL
import Database.Types import Database.Types
import Database.Keys.Handle import Database.Keys.Handle
import qualified Database.Queue as H import qualified Database.Queue as H
import Database.Init
import Annex.Locations import Annex.Locations
import Annex.Common hiding (delete) import Annex.Common hiding (delete)
import Annex.Version (versionUsesKeysDatabase) import Annex.Version (versionUsesKeysDatabase)
import qualified Annex import qualified Annex
import Annex.Perms
import Annex.LockFile import Annex.LockFile
import Utility.InodeCache import Utility.InodeCache
import Annex.InodeSentinal import Annex.InodeSentinal
@ -120,11 +120,7 @@ openDb createdb _ = catchPermissionDenied permerr $ withExclusiveLock gitAnnexKe
case (dbexists, createdb) of case (dbexists, createdb) of
(True, _) -> open db (True, _) -> open db
(False, True) -> do (False, True) -> do
liftIO $ do initDb db SQL.createTables
createDirectoryIfMissing True dbdir
H.initDb db SQL.createTables
setAnnexDirPerm dbdir
setAnnexFilePerm db
open db open db
(False, False) -> return DbUnavailable (False, False) -> return DbUnavailable
where where

View file

@ -9,7 +9,6 @@
module Database.Queue ( module Database.Queue (
DbQueue, DbQueue,
initDb,
openDbQueue, openDbQueue,
queryDbQueue, queryDbQueue,
closeDbQueue, closeDbQueue,

View file

@ -1,6 +1,6 @@
{- File mode utilities. {- File mode utilities.
- -
- Copyright 2010-2012 Joey Hess <id@joeyh.name> - Copyright 2010-2017 Joey Hess <id@joeyh.name>
- -
- License: BSD-2-clause - License: BSD-2-clause
-} -}
@ -130,6 +130,21 @@ withUmask umask a = bracket setup cleanup go
withUmask _ a = a withUmask _ a = a
#endif #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 :: [FileMode] -> FileMode
combineModes [] = 0 combineModes [] = 0
combineModes [m] = m combineModes [m] = m

View file

@ -81,3 +81,6 @@ extra copies.
In other words, Git-Annex and I are very happy together, and I'd like to 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 marry it. And because you are the father, I hereby respectfully ask for
your blessing. your blessing.
> [[fixed|done]] (and I suppose you have my blessing, but I'm not sure
> that's legal yet!) --[[Joey]]

View file

@ -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.
"""]]

View file

@ -798,6 +798,7 @@ Executable git-annex
Crypto Crypto
Database.Fsck Database.Fsck
Database.Handle Database.Handle
Database.Init
Database.Keys Database.Keys
Database.Keys.Handle Database.Keys.Handle
Database.Keys.SQL Database.Keys.SQL