2014-05-15 18:44:00 +00:00
|
|
|
{- Using ddar as a remote. Based on bup and rsync remotes.
|
|
|
|
-
|
2015-01-21 16:50:09 +00:00
|
|
|
- Copyright 2011 Joey Hess <id@joeyh.name>
|
2014-05-15 18:44:00 +00:00
|
|
|
- Copyright 2014 Robie Basak <robie@justgohome.co.uk>
|
|
|
|
-
|
2019-07-28 18:27:33 +00:00
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
2014-05-15 18:44:00 +00:00
|
|
|
-}
|
|
|
|
|
|
|
|
module Remote.Ddar (remote) where
|
|
|
|
|
|
|
|
import qualified Data.Map as M
|
2014-08-03 05:12:24 +00:00
|
|
|
import qualified Data.ByteString.Lazy as L
|
2014-05-15 18:44:00 +00:00
|
|
|
import System.IO.Error
|
|
|
|
|
2016-01-20 20:36:33 +00:00
|
|
|
import Annex.Common
|
2014-05-15 18:44:00 +00:00
|
|
|
import Types.Remote
|
|
|
|
import Types.Creds
|
|
|
|
import qualified Git
|
|
|
|
import Config
|
|
|
|
import Config.Cost
|
2020-01-14 19:41:34 +00:00
|
|
|
import Annex.SpecialRemote.Config
|
2014-05-15 18:44:00 +00:00
|
|
|
import Remote.Helper.Special
|
2019-02-20 19:55:01 +00:00
|
|
|
import Remote.Helper.ExportImport
|
2014-05-15 18:44:00 +00:00
|
|
|
import Annex.Ssh
|
|
|
|
import Annex.UUID
|
2017-08-18 02:11:31 +00:00
|
|
|
import Utility.SshHost
|
2020-01-10 18:10:20 +00:00
|
|
|
import Types.ProposedAccepted
|
2014-05-15 18:44:00 +00:00
|
|
|
|
2015-02-12 19:44:10 +00:00
|
|
|
data DdarRepo = DdarRepo
|
|
|
|
{ ddarRepoConfig :: RemoteGitConfig
|
|
|
|
, ddarRepoLocation :: String
|
|
|
|
}
|
2014-05-15 18:44:00 +00:00
|
|
|
|
|
|
|
remote :: RemoteType
|
2020-01-14 19:41:34 +00:00
|
|
|
remote = specialRemoteType $ RemoteType
|
2017-09-07 17:45:31 +00:00
|
|
|
{ typename = "ddar"
|
|
|
|
, enumerate = const (findSpecialRemotes "ddarrepo")
|
|
|
|
, generate = gen
|
2020-01-14 19:41:34 +00:00
|
|
|
, configParser = mkRemoteConfigParser
|
2020-01-20 19:20:04 +00:00
|
|
|
[ optionalStringParser ddarrepoField
|
|
|
|
(FieldDesc "(required) location of ddar archive to use")
|
|
|
|
]
|
2017-09-07 17:45:31 +00:00
|
|
|
, setup = ddarSetup
|
|
|
|
, exportSupported = exportUnsupported
|
2019-02-20 19:55:01 +00:00
|
|
|
, importSupported = importUnsupported
|
2017-09-07 17:45:31 +00:00
|
|
|
}
|
2014-05-15 18:44:00 +00:00
|
|
|
|
2020-01-14 19:41:34 +00:00
|
|
|
ddarrepoField :: RemoteConfigField
|
|
|
|
ddarrepoField = Accepted "ddarrepo"
|
|
|
|
|
|
|
|
gen :: Git.Repo -> UUID -> ParsedRemoteConfig -> RemoteGitConfig -> RemoteStateHandle -> Annex (Maybe Remote)
|
add RemoteStateHandle
This solves the problem of sameas remotes trampling over per-remote
state. Used for:
* per-remote state, of course
* per-remote metadata, also of course
* per-remote content identifiers, because two remote implementations
could in theory generate the same content identifier for two different
peices of content
While chunk logs are per-remote data, they don't use this, because the
number and size of chunks stored is a common property across sameas
remotes.
External special remote had a complication, where it was theoretically
possible for a remote to send SETSTATE or GETSTATE during INITREMOTE or
EXPORTSUPPORTED. Since the uuid of the remote is typically generate in
Remote.setup, it would only be possible to pass a Maybe
RemoteStateHandle into it, and it would otherwise have to construct its
own. Rather than go that route, I decided to send an ERROR in this case.
It seems unlikely that any existing external special remote will be
affected. They would have to make up a git-annex key, and set state for
some reason during INITREMOTE. I can imagine such a hack, but it doesn't
seem worth complicating the code in such an ugly way to support it.
Unfortunately, both TestRemote and Annex.Import needed the Remote
to have a new field added that holds its RemoteStateHandle.
2019-10-14 16:33:27 +00:00
|
|
|
gen r u c gc rs = do
|
2014-05-15 18:44:00 +00:00
|
|
|
cst <- remoteCost gc $
|
|
|
|
if ddarLocal ddarrepo
|
|
|
|
then nearlyCheapRemoteCost
|
|
|
|
else expensiveRemoteCost
|
2014-08-03 19:35:23 +00:00
|
|
|
return $ Just $ specialRemote' specialcfg c
|
2014-08-02 22:58:38 +00:00
|
|
|
(simplyPrepare $ store ddarrepo)
|
|
|
|
(simplyPrepare $ retrieve ddarrepo)
|
run Preparer to get Remover and CheckPresent actions
This will allow special remotes to eg, open a http connection and reuse it,
while checking if chunks are present, or removing chunks.
S3 and WebDAV both need this to support chunks with reasonable speed.
Note that a special remote might want to cache a http connection across
multiple requests. A simple case of this is that CheckPresent is typically
called before Store or Remove. A remote using this interface can certianly
use a Preparer that eg, uses a MVar to cache a http connection.
However, it's up to the remote to then deal with things like stale or
stalled http connections when eg, doing a series of downloads from a remote
and other places. There could be long delays between calls to a remote,
which could lead to eg, http connection stalls; the machine might even
move to a new network, etc.
It might be nice to improve this interface later to allow
the simple case without needing to handle the full complex case.
One way to do it would be to have a `Transaction SpecialRemote cache`,
where SpecialRemote contains methods for Storer, Retriever, Remover, and
CheckPresent, that all expect to be passed a `cache`.
2014-08-06 18:28:36 +00:00
|
|
|
(simplyPrepare $ remove ddarrepo)
|
|
|
|
(simplyPrepare $ checkKey ddarrepo)
|
2014-08-02 22:58:38 +00:00
|
|
|
(this cst)
|
|
|
|
where
|
|
|
|
this cst = Remote
|
2014-05-15 18:44:00 +00:00
|
|
|
{ uuid = u
|
|
|
|
, cost = cst
|
|
|
|
, name = Git.repoDescribe r
|
2014-08-02 22:58:38 +00:00
|
|
|
, storeKey = storeKeyDummy
|
|
|
|
, retrieveKeyFile = retreiveKeyFileDummy
|
2014-05-15 18:44:00 +00:00
|
|
|
, retrieveKeyFileCheap = retrieveCheap
|
2018-06-21 20:38:47 +00:00
|
|
|
-- ddar communicates over ssh, not subject to http redirect
|
|
|
|
-- type attacks
|
|
|
|
, retrievalSecurityPolicy = RetrievalAllKeysSecure
|
run Preparer to get Remover and CheckPresent actions
This will allow special remotes to eg, open a http connection and reuse it,
while checking if chunks are present, or removing chunks.
S3 and WebDAV both need this to support chunks with reasonable speed.
Note that a special remote might want to cache a http connection across
multiple requests. A simple case of this is that CheckPresent is typically
called before Store or Remove. A remote using this interface can certianly
use a Preparer that eg, uses a MVar to cache a http connection.
However, it's up to the remote to then deal with things like stale or
stalled http connections when eg, doing a series of downloads from a remote
and other places. There could be long delays between calls to a remote,
which could lead to eg, http connection stalls; the machine might even
move to a new network, etc.
It might be nice to improve this interface later to allow
the simple case without needing to handle the full complex case.
One way to do it would be to have a `Transaction SpecialRemote cache`,
where SpecialRemote contains methods for Storer, Retriever, Remover, and
CheckPresent, that all expect to be passed a `cache`.
2014-08-06 18:28:36 +00:00
|
|
|
, removeKey = removeKeyDummy
|
2015-10-08 19:01:38 +00:00
|
|
|
, lockContent = Nothing
|
run Preparer to get Remover and CheckPresent actions
This will allow special remotes to eg, open a http connection and reuse it,
while checking if chunks are present, or removing chunks.
S3 and WebDAV both need this to support chunks with reasonable speed.
Note that a special remote might want to cache a http connection across
multiple requests. A simple case of this is that CheckPresent is typically
called before Store or Remove. A remote using this interface can certianly
use a Preparer that eg, uses a MVar to cache a http connection.
However, it's up to the remote to then deal with things like stale or
stalled http connections when eg, doing a series of downloads from a remote
and other places. There could be long delays between calls to a remote,
which could lead to eg, http connection stalls; the machine might even
move to a new network, etc.
It might be nice to improve this interface later to allow
the simple case without needing to handle the full complex case.
One way to do it would be to have a `Transaction SpecialRemote cache`,
where SpecialRemote contains methods for Storer, Retriever, Remover, and
CheckPresent, that all expect to be passed a `cache`.
2014-08-06 18:28:36 +00:00
|
|
|
, checkPresent = checkPresentDummy
|
2014-08-06 17:45:19 +00:00
|
|
|
, checkPresentCheap = ddarLocal ddarrepo
|
2017-09-01 17:02:07 +00:00
|
|
|
, exportActions = exportUnsupported
|
2019-02-20 19:55:01 +00:00
|
|
|
, importActions = importUnsupported
|
2014-05-15 18:44:00 +00:00
|
|
|
, whereisKey = Nothing
|
|
|
|
, remoteFsck = Nothing
|
|
|
|
, repairRepo = Nothing
|
|
|
|
, config = c
|
2018-06-04 18:31:55 +00:00
|
|
|
, getRepo = return r
|
2014-05-15 18:44:00 +00:00
|
|
|
, gitconfig = gc
|
2015-02-12 19:44:10 +00:00
|
|
|
, localpath = if ddarLocal ddarrepo && not (null $ ddarRepoLocation ddarrepo)
|
|
|
|
then Just $ ddarRepoLocation ddarrepo
|
2014-05-15 18:44:00 +00:00
|
|
|
else Nothing
|
|
|
|
, remotetype = remote
|
|
|
|
, availability = if ddarLocal ddarrepo then LocallyAvailable else GloballyAvailable
|
|
|
|
, readonly = False
|
2018-08-30 15:12:18 +00:00
|
|
|
, appendonly = False
|
2014-08-10 18:52:58 +00:00
|
|
|
, mkUnavailable = return Nothing
|
2015-02-12 19:44:10 +00:00
|
|
|
, getInfo = return [("repo", ddarRepoLocation ddarrepo)]
|
2014-12-08 17:40:15 +00:00
|
|
|
, claimUrl = Nothing
|
2014-12-11 19:32:42 +00:00
|
|
|
, checkUrl = Nothing
|
add RemoteStateHandle
This solves the problem of sameas remotes trampling over per-remote
state. Used for:
* per-remote state, of course
* per-remote metadata, also of course
* per-remote content identifiers, because two remote implementations
could in theory generate the same content identifier for two different
peices of content
While chunk logs are per-remote data, they don't use this, because the
number and size of chunks stored is a common property across sameas
remotes.
External special remote had a complication, where it was theoretically
possible for a remote to send SETSTATE or GETSTATE during INITREMOTE or
EXPORTSUPPORTED. Since the uuid of the remote is typically generate in
Remote.setup, it would only be possible to pass a Maybe
RemoteStateHandle into it, and it would otherwise have to construct its
own. Rather than go that route, I decided to send an ERROR in this case.
It seems unlikely that any existing external special remote will be
affected. They would have to make up a git-annex key, and set state for
some reason during INITREMOTE. I can imagine such a hack, but it doesn't
seem worth complicating the code in such an ugly way to support it.
Unfortunately, both TestRemote and Annex.Import needed the Remote
to have a new field added that holds its RemoteStateHandle.
2019-10-14 16:33:27 +00:00
|
|
|
, remoteStateHandle = rs
|
2014-05-15 18:44:00 +00:00
|
|
|
}
|
2016-11-16 01:29:54 +00:00
|
|
|
ddarrepo = maybe (giveup "missing ddarrepo") (DdarRepo gc) (remoteAnnexDdarRepo gc)
|
2014-08-03 19:35:23 +00:00
|
|
|
specialcfg = (specialRemoteCfg c)
|
|
|
|
-- chunking would not improve ddar
|
|
|
|
{ chunkConfig = NoChunks
|
|
|
|
}
|
2014-05-15 18:44:00 +00:00
|
|
|
|
2017-02-07 18:35:58 +00:00
|
|
|
ddarSetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
|
|
|
|
ddarSetup _ mu _ c gc = do
|
2014-05-15 18:44:00 +00:00
|
|
|
u <- maybe (liftIO genUUID) return mu
|
|
|
|
|
|
|
|
-- verify configuration is sane
|
2020-01-10 18:10:20 +00:00
|
|
|
let ddarrepo = maybe (giveup "Specify ddarrepo=") fromProposedAccepted $
|
2020-01-14 19:41:34 +00:00
|
|
|
M.lookup ddarrepoField c
|
2016-05-23 21:27:15 +00:00
|
|
|
(c', _encsetup) <- encryptionSetup c gc
|
2014-05-15 18:44:00 +00:00
|
|
|
|
|
|
|
-- The ddarrepo is stored in git config, as well as this repo's
|
|
|
|
-- persistant state, so it can vary between hosts.
|
2018-03-27 16:41:57 +00:00
|
|
|
gitConfigSpecialRemote u c' [("ddarrepo", ddarrepo)]
|
2014-05-15 18:44:00 +00:00
|
|
|
|
|
|
|
return (c', u)
|
|
|
|
|
2014-08-02 22:58:38 +00:00
|
|
|
store :: DdarRepo -> Storer
|
|
|
|
store ddarrepo = fileStorer $ \k src _p -> do
|
2014-05-15 18:44:00 +00:00
|
|
|
let params =
|
|
|
|
[ Param "c"
|
|
|
|
, Param "-N"
|
2019-01-14 17:03:35 +00:00
|
|
|
, Param $ serializeKey k
|
2015-02-12 19:44:10 +00:00
|
|
|
, Param $ ddarRepoLocation ddarrepo
|
2014-05-15 18:44:00 +00:00
|
|
|
, File src
|
|
|
|
]
|
|
|
|
liftIO $ boolSystem "ddar" params
|
|
|
|
|
|
|
|
{- Convert remote DdarRepo to host and path on remote end -}
|
2017-08-18 02:11:31 +00:00
|
|
|
splitRemoteDdarRepo :: DdarRepo -> (SshHost, String)
|
|
|
|
splitRemoteDdarRepo ddarrepo = (either error id $ mkSshHost host, ddarrepo')
|
2014-05-15 18:44:00 +00:00
|
|
|
where
|
2015-02-12 19:44:10 +00:00
|
|
|
(host, remainder) = span (/= ':') (ddarRepoLocation ddarrepo)
|
2014-05-15 18:44:00 +00:00
|
|
|
ddarrepo' = drop 1 remainder
|
|
|
|
|
|
|
|
{- Return the command and parameters to use for a ddar call that may need to be
|
|
|
|
- made on a remote repository. This will call ssh if needed. -}
|
2017-02-15 19:08:46 +00:00
|
|
|
ddarRemoteCall :: ConsumeStdin -> DdarRepo -> Char -> [CommandParam] -> Annex (String, [CommandParam])
|
|
|
|
ddarRemoteCall cs ddarrepo cmd params
|
2014-05-15 18:44:00 +00:00
|
|
|
| ddarLocal ddarrepo = return ("ddar", localParams)
|
2017-03-17 20:02:47 +00:00
|
|
|
| otherwise = sshCommand cs (host, Nothing) (ddarRepoConfig ddarrepo) remoteCommand
|
2014-05-15 18:44:00 +00:00
|
|
|
where
|
|
|
|
(host, ddarrepo') = splitRemoteDdarRepo ddarrepo
|
2015-02-12 19:44:10 +00:00
|
|
|
localParams = Param [cmd] : Param (ddarRepoLocation ddarrepo) : params
|
2017-03-17 20:02:47 +00:00
|
|
|
remoteCommand = unwords $ map shellEscape $ toCommand $
|
|
|
|
[Param "ddar", Param [cmd], Param ddarrepo'] ++ params
|
2014-05-15 18:44:00 +00:00
|
|
|
|
|
|
|
{- Specialized ddarRemoteCall that includes extraction command and flags -}
|
2017-02-15 19:08:46 +00:00
|
|
|
ddarExtractRemoteCall :: ConsumeStdin -> DdarRepo -> Key -> Annex (String, [CommandParam])
|
|
|
|
ddarExtractRemoteCall cs ddarrepo k =
|
2019-01-14 17:03:35 +00:00
|
|
|
ddarRemoteCall cs ddarrepo 'x' [Param "--force-stdout", Param $ serializeKey k]
|
2014-05-15 18:44:00 +00:00
|
|
|
|
2014-08-02 22:58:38 +00:00
|
|
|
retrieve :: DdarRepo -> Retriever
|
2014-08-03 05:12:24 +00:00
|
|
|
retrieve ddarrepo = byteRetriever $ \k sink -> do
|
2017-02-15 19:08:46 +00:00
|
|
|
(cmd, params) <- ddarExtractRemoteCall NoConsumeStdin ddarrepo k
|
2014-08-03 05:12:24 +00:00
|
|
|
let p = (proc cmd $ toCommand params) { std_out = CreatePipe }
|
|
|
|
(_, Just h, _, pid) <- liftIO $ createProcess p
|
|
|
|
liftIO (hClose h >> forceSuccessProcess p pid)
|
|
|
|
`after` (sink =<< liftIO (L.hGetContents h))
|
2014-05-15 18:44:00 +00:00
|
|
|
|
2015-04-14 20:35:10 +00:00
|
|
|
retrieveCheap :: Key -> AssociatedFile -> FilePath -> Annex Bool
|
|
|
|
retrieveCheap _ _ _ = return False
|
2014-05-15 18:44:00 +00:00
|
|
|
|
run Preparer to get Remover and CheckPresent actions
This will allow special remotes to eg, open a http connection and reuse it,
while checking if chunks are present, or removing chunks.
S3 and WebDAV both need this to support chunks with reasonable speed.
Note that a special remote might want to cache a http connection across
multiple requests. A simple case of this is that CheckPresent is typically
called before Store or Remove. A remote using this interface can certianly
use a Preparer that eg, uses a MVar to cache a http connection.
However, it's up to the remote to then deal with things like stale or
stalled http connections when eg, doing a series of downloads from a remote
and other places. There could be long delays between calls to a remote,
which could lead to eg, http connection stalls; the machine might even
move to a new network, etc.
It might be nice to improve this interface later to allow
the simple case without needing to handle the full complex case.
One way to do it would be to have a `Transaction SpecialRemote cache`,
where SpecialRemote contains methods for Storer, Retriever, Remover, and
CheckPresent, that all expect to be passed a `cache`.
2014-08-06 18:28:36 +00:00
|
|
|
remove :: DdarRepo -> Remover
|
2014-05-15 18:44:00 +00:00
|
|
|
remove ddarrepo key = do
|
2017-02-15 19:08:46 +00:00
|
|
|
(cmd, params) <- ddarRemoteCall NoConsumeStdin ddarrepo 'd'
|
2019-01-14 17:03:35 +00:00
|
|
|
[Param $ serializeKey key]
|
2014-05-15 18:44:00 +00:00
|
|
|
liftIO $ boolSystem cmd params
|
|
|
|
|
|
|
|
ddarDirectoryExists :: DdarRepo -> Annex (Either String Bool)
|
|
|
|
ddarDirectoryExists ddarrepo
|
|
|
|
| ddarLocal ddarrepo = do
|
2015-02-12 19:44:10 +00:00
|
|
|
maybeStatus <- liftIO $ tryJust (guard . isDoesNotExistError) $ getFileStatus $ ddarRepoLocation ddarrepo
|
2014-05-15 18:44:00 +00:00
|
|
|
return $ case maybeStatus of
|
|
|
|
Left _ -> Right False
|
|
|
|
Right status -> Right $ isDirectory status
|
|
|
|
| otherwise = do
|
2017-03-17 20:02:47 +00:00
|
|
|
let remotecmd = unwords $ map shellEscape
|
|
|
|
[ "test", "-d", ddarrepo' ]
|
|
|
|
(sshcmd, sshps) <- sshCommand NoConsumeStdin (host, Nothing)
|
|
|
|
(ddarRepoConfig ddarrepo) remotecmd
|
|
|
|
exitCode <- liftIO $ safeSystem sshcmd sshps
|
2014-05-15 18:44:00 +00:00
|
|
|
case exitCode of
|
|
|
|
ExitSuccess -> return $ Right True
|
|
|
|
ExitFailure 1 -> return $ Right False
|
2017-03-17 20:02:47 +00:00
|
|
|
ExitFailure code -> return $ Left $ "ssh " ++
|
|
|
|
show (unwords $ toCommand sshps) ++
|
2014-05-15 18:44:00 +00:00
|
|
|
" failed with status " ++ show code
|
|
|
|
where
|
|
|
|
(host, ddarrepo') = splitRemoteDdarRepo ddarrepo
|
|
|
|
|
|
|
|
{- Use "ddar t" to determine if a given key is present in a ddar archive -}
|
|
|
|
inDdarManifest :: DdarRepo -> Key -> Annex (Either String Bool)
|
|
|
|
inDdarManifest ddarrepo k = do
|
2017-02-15 19:08:46 +00:00
|
|
|
(cmd, params) <- ddarRemoteCall NoConsumeStdin ddarrepo 't' []
|
2014-05-15 18:44:00 +00:00
|
|
|
let p = proc cmd $ toCommand params
|
|
|
|
liftIO $ catchMsgIO $ withHandle StdoutHandle createProcessSuccess p $ \h -> do
|
|
|
|
contents <- hGetContents h
|
|
|
|
return $ elem k' $ lines contents
|
|
|
|
where
|
2019-01-14 17:03:35 +00:00
|
|
|
k' = serializeKey k
|
2014-05-15 18:44:00 +00:00
|
|
|
|
run Preparer to get Remover and CheckPresent actions
This will allow special remotes to eg, open a http connection and reuse it,
while checking if chunks are present, or removing chunks.
S3 and WebDAV both need this to support chunks with reasonable speed.
Note that a special remote might want to cache a http connection across
multiple requests. A simple case of this is that CheckPresent is typically
called before Store or Remove. A remote using this interface can certianly
use a Preparer that eg, uses a MVar to cache a http connection.
However, it's up to the remote to then deal with things like stale or
stalled http connections when eg, doing a series of downloads from a remote
and other places. There could be long delays between calls to a remote,
which could lead to eg, http connection stalls; the machine might even
move to a new network, etc.
It might be nice to improve this interface later to allow
the simple case without needing to handle the full complex case.
One way to do it would be to have a `Transaction SpecialRemote cache`,
where SpecialRemote contains methods for Storer, Retriever, Remover, and
CheckPresent, that all expect to be passed a `cache`.
2014-08-06 18:28:36 +00:00
|
|
|
checkKey :: DdarRepo -> CheckPresent
|
2014-08-06 17:45:19 +00:00
|
|
|
checkKey ddarrepo key = do
|
2014-05-15 18:44:00 +00:00
|
|
|
directoryExists <- ddarDirectoryExists ddarrepo
|
|
|
|
case directoryExists of
|
2014-08-06 17:45:19 +00:00
|
|
|
Left e -> error e
|
|
|
|
Right True -> either error return
|
|
|
|
=<< inDdarManifest ddarrepo key
|
|
|
|
Right False -> return False
|
2014-05-15 18:44:00 +00:00
|
|
|
|
|
|
|
ddarLocal :: DdarRepo -> Bool
|
2015-02-12 19:44:10 +00:00
|
|
|
ddarLocal = notElem ':' . ddarRepoLocation
|