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:
parent
d2174915c0
commit
3b22ad9f47
10 changed files with 135 additions and 42 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
55
Database/Init.hs
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
|
|
||||||
module Database.Queue (
|
module Database.Queue (
|
||||||
DbQueue,
|
DbQueue,
|
||||||
initDb,
|
|
||||||
openDbQueue,
|
openDbQueue,
|
||||||
queryDbQueue,
|
queryDbQueue,
|
||||||
closeDbQueue,
|
closeDbQueue,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]]
|
||||||
|
|
|
@ -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.
|
||||||
|
"""]]
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue