2019-02-20 19:55:01 +00:00
|
|
|
{- Helper to make remotes support export and import (or not).
|
2017-09-01 17:02:07 +00:00
|
|
|
-
|
2019-02-20 19:55:01 +00:00
|
|
|
- Copyright 2017-2019 Joey Hess <id@joeyh.name>
|
2017-09-01 17:02:07 +00:00
|
|
|
-
|
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
2019-03-04 21:50:41 +00:00
|
|
|
{-# LANGUAGE FlexibleInstances, FlexibleContexts #-}
|
2017-09-07 17:45:31 +00:00
|
|
|
|
2019-02-20 19:55:01 +00:00
|
|
|
module Remote.Helper.ExportImport where
|
2017-09-01 17:02:07 +00:00
|
|
|
|
|
|
|
import Annex.Common
|
|
|
|
import Types.Remote
|
2017-09-04 20:39:56 +00:00
|
|
|
import Types.Backend
|
|
|
|
import Types.Key
|
|
|
|
import Backend
|
2017-09-04 16:40:33 +00:00
|
|
|
import Remote.Helper.Encryptable (isEncrypted)
|
2019-03-04 21:50:41 +00:00
|
|
|
import qualified Database.Export as Export
|
|
|
|
import qualified Database.ContentIdentifier as ContentIdentifier
|
2017-09-19 18:20:47 +00:00
|
|
|
import Annex.Export
|
2019-03-04 20:02:56 +00:00
|
|
|
import Annex.Import
|
2019-03-04 21:50:41 +00:00
|
|
|
import Annex.LockFile
|
2018-10-10 15:07:49 +00:00
|
|
|
import Config
|
2018-12-03 18:15:15 +00:00
|
|
|
import Git.Types (fromRef)
|
|
|
|
import Logs.Export
|
2019-03-04 22:20:12 +00:00
|
|
|
import Logs.ContentIdentifier (recordContentIdentifier)
|
2017-09-01 17:02:07 +00:00
|
|
|
|
2017-09-04 16:40:33 +00:00
|
|
|
import qualified Data.Map as M
|
2017-09-18 22:40:16 +00:00
|
|
|
import Control.Concurrent.STM
|
2017-09-04 16:40:33 +00:00
|
|
|
|
|
|
|
-- | Use for remotes that do not support exports.
|
2017-09-07 17:45:31 +00:00
|
|
|
class HasExportUnsupported a where
|
|
|
|
exportUnsupported :: a
|
2017-09-04 16:40:33 +00:00
|
|
|
|
2017-09-07 17:45:31 +00:00
|
|
|
instance HasExportUnsupported (RemoteConfig -> RemoteGitConfig -> Annex Bool) where
|
|
|
|
exportUnsupported = \_ _ -> return False
|
|
|
|
|
2019-01-30 18:55:28 +00:00
|
|
|
instance HasExportUnsupported (ExportActions Annex) where
|
|
|
|
exportUnsupported = ExportActions
|
2017-09-08 18:24:05 +00:00
|
|
|
{ storeExport = \_ _ _ _ -> do
|
|
|
|
warning "store export is unsupported"
|
|
|
|
return False
|
|
|
|
, retrieveExport = \_ _ _ _ -> return False
|
2017-09-07 17:45:31 +00:00
|
|
|
, checkPresentExport = \_ _ -> return False
|
2017-09-15 17:15:47 +00:00
|
|
|
, removeExport = \_ _ -> return False
|
|
|
|
, removeExportDirectory = Just $ \_ -> return False
|
2017-09-07 17:45:31 +00:00
|
|
|
, renameExport = \_ _ _ -> return False
|
|
|
|
}
|
|
|
|
|
2019-02-20 19:55:01 +00:00
|
|
|
-- | Use for remotes that do not support imports.
|
|
|
|
class HasImportUnsupported a where
|
|
|
|
importUnsupported :: a
|
|
|
|
|
|
|
|
instance HasImportUnsupported (RemoteConfig -> RemoteGitConfig -> Annex Bool) where
|
|
|
|
importUnsupported = \_ _ -> return False
|
|
|
|
|
|
|
|
instance HasImportUnsupported (ImportActions Annex) where
|
|
|
|
importUnsupported = ImportActions
|
2019-02-21 17:38:27 +00:00
|
|
|
{ listImportableContents = return Nothing
|
2019-02-27 17:15:02 +00:00
|
|
|
, retrieveExportWithContentIdentifier = \_ _ _ _ _ -> return Nothing
|
2019-02-20 19:55:01 +00:00
|
|
|
, storeExportWithContentIdentifier = \_ _ _ _ _ -> return Nothing
|
|
|
|
}
|
|
|
|
|
2017-09-07 17:45:31 +00:00
|
|
|
exportIsSupported :: RemoteConfig -> RemoteGitConfig -> Annex Bool
|
|
|
|
exportIsSupported = \_ _ -> return True
|
|
|
|
|
2019-03-04 20:02:56 +00:00
|
|
|
importIsSupported :: RemoteConfig -> RemoteGitConfig -> Annex Bool
|
|
|
|
importIsSupported = \_ _ -> return True
|
|
|
|
|
|
|
|
-- | Prevent or allow exporttree=yes and importtree=yes when
|
|
|
|
-- setting up a new remote, depending on exportSupported and importSupported.
|
|
|
|
adjustExportImportRemoteType :: RemoteType -> RemoteType
|
|
|
|
adjustExportImportRemoteType rt = rt { setup = setup' }
|
2017-09-07 17:45:31 +00:00
|
|
|
where
|
2019-03-04 20:02:56 +00:00
|
|
|
setup' st mu cp c gc =
|
|
|
|
let checkconfig supported configured setting cont =
|
|
|
|
ifM (supported rt c gc)
|
|
|
|
( case st of
|
|
|
|
Init
|
|
|
|
| configured c && isEncrypted c ->
|
|
|
|
giveup $ "cannot enable both encryption and " ++ setting
|
|
|
|
| otherwise -> cont
|
|
|
|
Enable oldc
|
|
|
|
| configured c /= configured oldc ->
|
|
|
|
giveup $ "cannot change " ++ setting ++ " of existing special remote"
|
|
|
|
| otherwise -> cont
|
|
|
|
, if configured c
|
|
|
|
then giveup $ setting ++ " is not supported by this special remote"
|
|
|
|
else cont
|
|
|
|
)
|
|
|
|
in checkconfig exportSupported exportTree "exporttree" $
|
|
|
|
checkconfig importSupported importTree "importtree" $
|
|
|
|
if importTree c && not (exportTree c)
|
|
|
|
then giveup "cannot enable importtree=yes without also enabling exporttree=yes"
|
|
|
|
else setup rt st mu cp c gc
|
|
|
|
|
2019-03-04 21:50:41 +00:00
|
|
|
-- | Adjust a remote to support exporttree=yes and importree=yes.
|
2019-03-04 20:02:56 +00:00
|
|
|
--
|
2019-03-04 21:50:41 +00:00
|
|
|
-- Note that all remotes with importree=yes also have exporttree=yes.
|
|
|
|
adjustExportImport :: Remote -> Annex Remote
|
|
|
|
adjustExportImport r = case M.lookup "exporttree" (config r) of
|
|
|
|
Nothing -> return $ notexport r
|
2018-10-10 15:07:49 +00:00
|
|
|
Just c -> case yesNo c of
|
|
|
|
Just True -> ifM (isExportSupported r)
|
2019-03-04 21:50:41 +00:00
|
|
|
( do
|
2019-03-04 22:10:24 +00:00
|
|
|
exportdbv <- liftIO $ newTVarIO Nothing
|
|
|
|
r' <- isexport exportdbv
|
2019-03-04 21:50:41 +00:00
|
|
|
if importTree (config r)
|
2019-03-04 22:10:24 +00:00
|
|
|
then isimport r' exportdbv
|
2019-03-04 21:50:41 +00:00
|
|
|
else return r'
|
|
|
|
, return $ notexport r
|
2018-10-10 15:07:49 +00:00
|
|
|
)
|
2019-03-04 21:50:41 +00:00
|
|
|
Just False -> return $ notexport r
|
2018-10-10 15:07:49 +00:00
|
|
|
Nothing -> do
|
|
|
|
warning $ "bad exporttree value for " ++ name r ++ ", assuming not an export"
|
2019-03-04 21:50:41 +00:00
|
|
|
return $ notexport r
|
2017-09-07 17:45:31 +00:00
|
|
|
where
|
2019-03-04 21:50:41 +00:00
|
|
|
notexport r' = notimport r'
|
2017-09-13 16:05:53 +00:00
|
|
|
{ exportActions = exportUnsupported
|
2019-03-04 21:50:41 +00:00
|
|
|
, remotetype = (remotetype r')
|
2017-09-13 16:05:53 +00:00
|
|
|
{ exportSupported = exportUnsupported
|
|
|
|
}
|
|
|
|
}
|
2019-03-04 21:50:41 +00:00
|
|
|
|
|
|
|
notimport r' = r'
|
|
|
|
{ importActions = importUnsupported
|
|
|
|
, remotetype = (remotetype r')
|
|
|
|
{ importSupported = importUnsupported
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-04 22:10:24 +00:00
|
|
|
isimport r' exportdbv = do
|
2019-03-04 21:50:41 +00:00
|
|
|
lcklckv <- liftIO newEmptyTMVarIO
|
|
|
|
dbtv <- liftIO newEmptyTMVarIO
|
|
|
|
let store f k loc p = do
|
2019-03-04 22:20:12 +00:00
|
|
|
-- Only open the database once it's needed,
|
|
|
|
-- and take an exclusive write lock.
|
|
|
|
-- The write lock will then remain held while the
|
2019-03-04 21:50:41 +00:00
|
|
|
-- process is running.
|
|
|
|
db <- liftIO (atomically (tryReadTMVar dbtv)) >>= \case
|
|
|
|
Just (db, _lck) -> return db
|
|
|
|
-- let only one thread take the lock
|
|
|
|
Nothing -> ifM (liftIO $ atomically $ tryPutTMVar lcklckv ())
|
|
|
|
( do
|
|
|
|
lck <- takeExclusiveLock gitAnnexContentIdentifierLock
|
|
|
|
db <- ContentIdentifier.openDb
|
|
|
|
liftIO $ atomically (putTMVar dbtv (db, lck))
|
|
|
|
return db
|
|
|
|
-- loser waits for winner to open
|
|
|
|
-- the db and can then also use its
|
|
|
|
-- handle
|
|
|
|
, liftIO $ fst <$> atomically (readTMVar dbtv)
|
|
|
|
)
|
|
|
|
|
2019-03-04 22:10:24 +00:00
|
|
|
exportdb <- getexportdb exportdbv
|
2019-03-04 21:50:41 +00:00
|
|
|
ks <- liftIO $ Export.getExportedKey exportdb loc
|
|
|
|
oldcids <- liftIO $ concat
|
|
|
|
<$> mapM (ContentIdentifier.getContentIdentifiers db (uuid r')) ks
|
|
|
|
storeExportWithContentIdentifier (importActions r') f k loc oldcids p >>= \case
|
|
|
|
Nothing -> return False
|
|
|
|
Just newcid -> do
|
|
|
|
liftIO $ ContentIdentifier.recordContentIdentifier db (uuid r') newcid k
|
2019-03-04 22:20:12 +00:00
|
|
|
recordContentIdentifier (uuid r') newcid k
|
2019-03-04 21:50:41 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
return $ r'
|
|
|
|
{ exportActions = (exportActions r')
|
|
|
|
{ storeExport = store
|
|
|
|
}
|
|
|
|
}
|
2019-03-04 22:10:24 +00:00
|
|
|
|
|
|
|
getexportdb dbv = liftIO (atomically (readTVar dbv)) >>= \case
|
|
|
|
Just db -> return db
|
|
|
|
Nothing -> do
|
|
|
|
db <- Export.openDb (uuid r)
|
|
|
|
liftIO $ atomically $ writeTVar dbv $ Just db
|
|
|
|
return db
|
2019-03-04 21:50:41 +00:00
|
|
|
|
2019-03-04 22:10:24 +00:00
|
|
|
isexport dbv = do
|
2018-10-22 16:59:10 +00:00
|
|
|
updateflag <- liftIO $ newTVarIO Nothing
|
2017-09-18 22:40:16 +00:00
|
|
|
|
2018-10-22 16:59:10 +00:00
|
|
|
-- When multiple threads run this, all except the first
|
|
|
|
-- will block until the first runs doneupdateonce.
|
|
|
|
-- Returns True when an update should be done and False
|
|
|
|
-- when the update has already been done.
|
|
|
|
let startupdateonce = liftIO $ atomically $
|
|
|
|
readTVar updateflag >>= \case
|
|
|
|
Nothing -> do
|
|
|
|
writeTVar updateflag (Just True)
|
2017-09-18 22:40:16 +00:00
|
|
|
return True
|
2018-10-22 16:59:10 +00:00
|
|
|
Just True -> retry
|
|
|
|
Just False -> return False
|
|
|
|
let doneupdateonce = \updated ->
|
|
|
|
when updated $
|
|
|
|
liftIO $ atomically $
|
|
|
|
writeTVar updateflag (Just False)
|
2017-09-18 22:40:16 +00:00
|
|
|
|
2018-11-13 19:50:06 +00:00
|
|
|
exportinconflict <- liftIO $ newTVarIO False
|
|
|
|
|
2017-09-18 22:40:16 +00:00
|
|
|
-- Get export locations for a key. Checks once
|
|
|
|
-- if the export log is different than the database and
|
|
|
|
-- updates the database, to notice when an export has been
|
|
|
|
-- updated from another repository.
|
|
|
|
let getexportlocs = \k -> do
|
2019-03-04 22:10:24 +00:00
|
|
|
db <- getexportdb dbv
|
2018-10-22 16:59:10 +00:00
|
|
|
bracket startupdateonce doneupdateonce $ \updatenow ->
|
|
|
|
when updatenow $
|
2019-03-04 21:50:41 +00:00
|
|
|
Export.updateExportTreeFromLog db >>= \case
|
|
|
|
Export.ExportUpdateSuccess -> return ()
|
|
|
|
Export.ExportUpdateConflict -> do
|
2018-11-13 19:50:06 +00:00
|
|
|
warnExportConflict r
|
|
|
|
liftIO $ atomically $
|
|
|
|
writeTVar exportinconflict True
|
2019-03-04 21:50:41 +00:00
|
|
|
liftIO $ Export.getExportTree db k
|
2017-09-18 22:40:16 +00:00
|
|
|
|
2017-09-07 17:45:31 +00:00
|
|
|
return $ r
|
2017-09-18 18:40:08 +00:00
|
|
|
-- Storing a key on an export could be implemented,
|
|
|
|
-- but it would perform unncessary work
|
|
|
|
-- when another repository has already stored the
|
|
|
|
-- key, and the local repository does not know
|
|
|
|
-- about it. To avoid unnecessary costs, don't do it.
|
2017-09-04 20:39:56 +00:00
|
|
|
{ storeKey = \_ _ _ -> do
|
|
|
|
warning "remote is configured with exporttree=yes; use `git-annex export` to store content on it"
|
|
|
|
return False
|
2018-08-30 15:41:44 +00:00
|
|
|
-- Keys can be retrieved using retrieveExport,
|
|
|
|
-- but since that retrieves from a path in the
|
|
|
|
-- remote that another writer could have replaced
|
|
|
|
-- with content not of the requested key,
|
|
|
|
-- the content has to be strongly verified.
|
|
|
|
--
|
2019-01-30 17:23:03 +00:00
|
|
|
-- appendonly remotes have a key/value store,
|
|
|
|
-- so don't need to use retrieveExport. However,
|
|
|
|
-- fall back to it if retrieveKeyFile fails.
|
|
|
|
, retrieveKeyFile = \k af dest p ->
|
|
|
|
let retrieveexport = retrieveKeyFileFromExport getexportlocs exportinconflict k af dest p
|
|
|
|
in if appendonly r
|
|
|
|
then do
|
|
|
|
ret@(ok, _v) <- retrieveKeyFile r k af dest p
|
|
|
|
if ok
|
|
|
|
then return ret
|
|
|
|
else retrieveexport
|
|
|
|
else retrieveexport
|
2018-08-30 15:41:44 +00:00
|
|
|
, retrieveKeyFileCheap = if appendonly r
|
|
|
|
then retrieveKeyFileCheap r
|
|
|
|
else \_ _ _ -> return False
|
2017-09-17 21:56:33 +00:00
|
|
|
-- Removing a key from an export would need to
|
|
|
|
-- change the tree in the export log to not include
|
|
|
|
-- the file. Otherwise, conflicts when removing
|
|
|
|
-- files would not be dealt with correctly.
|
|
|
|
-- There does not seem to be a good use case for
|
|
|
|
-- removing a key from an export in any case.
|
|
|
|
, removeKey = \_k -> do
|
|
|
|
warning "dropping content from an export is not supported; use `git annex export` to export a tree that lacks the files you want to remove"
|
|
|
|
return False
|
2017-09-04 20:39:56 +00:00
|
|
|
-- Can't lock content on exports, since they're
|
|
|
|
-- not key/value stores, and someone else could
|
|
|
|
-- change what's exported to a file at any time.
|
2018-08-30 15:41:44 +00:00
|
|
|
--
|
|
|
|
-- (except for appendonly remotes)
|
|
|
|
, lockContent = if appendonly r
|
|
|
|
then lockContent r
|
|
|
|
else Nothing
|
|
|
|
-- Check if any of the files a key was exported to
|
|
|
|
-- are present. This doesn't guarantee the export
|
|
|
|
-- contains the right content, which is why export
|
|
|
|
-- remotes are untrusted.
|
|
|
|
--
|
|
|
|
-- (but appendonly remotes work the same as any
|
|
|
|
-- non-export remote)
|
|
|
|
, checkPresent = if appendonly r
|
|
|
|
then checkPresent r
|
2019-01-30 18:55:28 +00:00
|
|
|
else \k -> anyM (checkPresentExport (exportActions r) k)
|
|
|
|
=<< getexportlocs k
|
2018-11-13 19:50:06 +00:00
|
|
|
-- checkPresent from an export is more expensive
|
|
|
|
-- than otherwise, so not cheap. Also, this
|
|
|
|
-- avoids things that look at checkPresentCheap and
|
|
|
|
-- silently skip non-present files from behaving
|
|
|
|
-- in confusing ways when there's an export
|
|
|
|
-- conflict.
|
|
|
|
, checkPresentCheap = False
|
2017-09-04 20:55:31 +00:00
|
|
|
, mkUnavailable = return Nothing
|
|
|
|
, getInfo = do
|
2019-01-30 16:36:30 +00:00
|
|
|
ts <- map fromRef . exportedTreeishes
|
2018-12-03 18:15:15 +00:00
|
|
|
<$> getExport (uuid r)
|
2017-09-04 20:55:31 +00:00
|
|
|
is <- getInfo r
|
2018-12-03 18:43:59 +00:00
|
|
|
return (is++[("export", "yes"), ("exportedtree", unwords ts)])
|
2017-09-04 20:39:56 +00:00
|
|
|
}
|
2018-11-13 19:50:06 +00:00
|
|
|
retrieveKeyFileFromExport getexportlocs exportinconflict k _af dest p = unVerified $
|
2018-08-30 15:41:44 +00:00
|
|
|
if maybe False (isJust . verifyKeyContent) (maybeLookupBackendVariety (keyVariety k))
|
|
|
|
then do
|
|
|
|
locs <- getexportlocs k
|
|
|
|
case locs of
|
|
|
|
[] -> do
|
2018-11-13 19:50:06 +00:00
|
|
|
ifM (liftIO $ atomically $ readTVar exportinconflict)
|
|
|
|
( warning "unknown export location, likely due to the export conflict"
|
|
|
|
, warning "unknown export location"
|
|
|
|
)
|
2018-08-30 15:41:44 +00:00
|
|
|
return False
|
2019-01-30 18:55:28 +00:00
|
|
|
(l:_) -> retrieveExport (exportActions r) k l dest p
|
2018-08-30 15:41:44 +00:00
|
|
|
else do
|
2019-01-11 20:34:04 +00:00
|
|
|
warning $ "exported content cannot be verified due to using the " ++ decodeBS (formatKeyVariety (keyVariety k)) ++ " backend"
|
2018-08-30 15:41:44 +00:00
|
|
|
return False
|