E variant of external backend keys

This commit is contained in:
Joey Hess 2020-07-29 17:12:22 -04:00
parent b6fa4cb42f
commit ea63d1dfe3
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
5 changed files with 75 additions and 41 deletions

View file

@ -11,6 +11,7 @@ module Backend.External (makeBackend) where
import Annex.Common import Annex.Common
import Annex.ExternalAddonProcess import Annex.ExternalAddonProcess
import Backend.Utilities
import Types.Key import Types.Key
import Types.Backend import Types.Backend
import Types.KeySource import Types.KeySource
@ -19,6 +20,7 @@ import qualified Utility.SimpleProtocol as Proto
import qualified Data.ByteString as S import qualified Data.ByteString as S
import qualified Data.Map.Strict as M import qualified Data.Map.Strict as M
import Data.Char
import Control.Concurrent import Control.Concurrent
import System.IO.Unsafe (unsafePerformIO) import System.IO.Unsafe (unsafePerformIO)
import System.Log.Logger (debugM) import System.Log.Logger (debugM)
@ -92,8 +94,7 @@ genKeyExternal ebname hasext ks meterupdate =
req = GENKEY (fromRawFilePath (contentLocation ks)) req = GENKEY (fromRawFilePath (contentLocation ks))
notavail = giveup $ "Cannot generate a key, since " ++ externalBackendProgram ebname ++ " is not available." notavail = giveup $ "Cannot generate a key, since " ++ externalBackendProgram ebname ++ " is not available."
-- TODO hasExt handling go (GENKEY_SUCCESS pk) = Just $ Result <$> fromProtoKey pk hasext ks
go (GENKEY_SUCCESS (ProtoKey k)) = result k
go (GENKEY_FAILURE msg) = Just $ giveup $ go (GENKEY_FAILURE msg) = Just $ giveup $
"External backend program failed to generate a key: " ++ msg "External backend program failed to generate a key: " ++ msg
go (PROGRESS bytesprocessed) = Just $ do go (PROGRESS bytesprocessed) = Just $ do
@ -106,8 +107,7 @@ verifyKeyContentExternal ebname hasext meterupdate k f =
withExternalState ebname hasext $ \st -> withExternalState ebname hasext $ \st ->
handleRequest st req notavail go handleRequest st req notavail go
where where
-- TODO hasExt handling req = VERIFYKEYCONTENT (toProtoKey k) f
req = VERIFYKEYCONTENT (ProtoKey k) f
-- This should not be able to happen, because CANVERIFY is checked -- This should not be able to happen, because CANVERIFY is checked
-- before this function is enable, and so the external program -- before this function is enable, and so the external program
@ -273,6 +273,26 @@ withExternalState bname hasext a = do
newtype ProtoKey = ProtoKey Key newtype ProtoKey = ProtoKey Key
deriving (Show) deriving (Show)
fromProtoKey :: ProtoKey -> HasExt -> KeySource -> Annex Key
fromProtoKey (ProtoKey k) (HasExt False) _ = pure k
fromProtoKey (ProtoKey k) hasext@(HasExt True) source =
addE source (setHasExt hasext) k
toProtoKey :: Key -> ProtoKey
toProtoKey k = ProtoKey $ alterKey k $ \d -> d
-- The extension can be easily removed, because the protocol
-- documentation does not allow '.' to be used in the keyName,
-- so the first one is the extension.
{ keyName = S.takeWhile (/= dot) (keyName d)
, keyVariety = setHasExt (HasExt False) (keyVariety d)
}
where
dot = fromIntegral (ord '.')
setHasExt :: HasExt -> KeyVariety -> KeyVariety
setHasExt hasext (ExternalKey name _) = ExternalKey name hasext
setHasExt _ v = v
instance Proto.Serializable ProtoKey where instance Proto.Serializable ProtoKey where
serialize (ProtoKey k) = Proto.serialize k serialize (ProtoKey k) = Proto.serialize k
deserialize = fmap ProtoKey . Proto.deserialize deserialize = fmap ProtoKey . Proto.deserialize

View file

@ -20,13 +20,11 @@ import Types.Backend
import Types.KeySource import Types.KeySource
import Utility.Hash import Utility.Hash
import Utility.Metered import Utility.Metered
import Backend.Utilities
import qualified Data.ByteString as S import qualified Data.ByteString as S
import qualified Data.ByteString.Char8 as S8 import qualified Data.ByteString.Char8 as S8
import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy as L
import qualified System.FilePath.ByteString as P
import Data.Char
import Data.Word
import Control.DeepSeq import Control.DeepSeq
import Control.Exception (evaluate) import Control.Exception (evaluate)
@ -114,29 +112,8 @@ keyValue hash source meterupdate = do
{- Extension preserving keys. -} {- Extension preserving keys. -}
keyValueE :: Hash -> KeySource -> MeterUpdate -> Annex Key keyValueE :: Hash -> KeySource -> MeterUpdate -> Annex Key
keyValueE hash source meterupdate = keyValueE hash source meterupdate =
keyValue hash source meterupdate >>= addE keyValue hash source meterupdate
where >>= addE source (const $ hashKeyVariety hash (HasExt True))
addE k = do
maxlen <- annexMaxExtensionLength <$> Annex.getGitConfig
let ext = selectExtension maxlen (keyFilename source)
return $ alterKey k $ \d -> d
{ keyName = keyName d <> ext
, keyVariety = hashKeyVariety hash (HasExt True)
}
selectExtension :: Maybe Int -> RawFilePath -> S.ByteString
selectExtension maxlen f
| null es = ""
| otherwise = S.intercalate "." ("":es)
where
es = filter (not . S.null) $ reverse $
take 2 $ filter (S.all validInExtension) $
takeWhile shortenough $
reverse $ S.split (fromIntegral (ord '.')) (P.takeExtensions f)
shortenough e = S.length e <= fromMaybe maxExtensionLen maxlen
maxExtensionLen :: Int
maxExtensionLen = 4 -- long enough for "jpeg"
{- A key's checksum is checked during fsck when it's content is present {- A key's checksum is checked during fsck when it's content is present
- except for in fast mode. -} - except for in fast mode. -}
@ -166,13 +143,6 @@ checkKeyChecksum hash key file = catchIOErrorType HardwareFault hwfault $ do
keyHash :: Key -> S.ByteString keyHash :: Key -> S.ByteString
keyHash = fst . splitKeyNameExtension keyHash = fst . splitKeyNameExtension
validInExtension :: Word8 -> Bool
validInExtension c
| isAlphaNum (chr (fromIntegral c)) = True
| fromIntegral c == ord '.' = True
| c <= 127 = False -- other ascii: spaces, punctuation, control chars
| otherwise = True -- utf8 is allowed, also other encodings
{- Upgrade keys that have the \ prefix on their hash due to a bug, or {- Upgrade keys that have the \ prefix on their hash due to a bug, or
- that contain non-alphanumeric characters in their extension. - that contain non-alphanumeric characters in their extension.
- -
@ -310,10 +280,10 @@ testKeyBackend =
let b = genBackendE (SHA2Hash (HashSize 256)) let b = genBackendE (SHA2Hash (HashSize 256))
gk = case genKey b of gk = case genKey b of
Nothing -> Nothing Nothing -> Nothing
Just f -> Just (\ks p -> addE <$> f ks p) Just f -> Just (\ks p -> addTestE <$> f ks p)
in b { genKey = gk } in b { genKey = gk }
where where
addE k = alterKey k $ \d -> d addTestE k = alterKey k $ \d -> d
{ keyName = keyName d <> longext { keyName = keyName d <> longext
} }
longext = ".this-is-a-test-key" longext = ".this-is-a-test-key"

View file

@ -1,17 +1,25 @@
{- git-annex backend utilities {- git-annex backend utilities
- -
- Copyright 2012-2019 Joey Hess <id@joeyh.name> - Copyright 2012-2020 Joey Hess <id@joeyh.name>
- -
- Licensed under the GNU AGPL version 3 or higher. - Licensed under the GNU AGPL version 3 or higher.
-} -}
{-# LANGUAGE OverloadedStrings #-}
module Backend.Utilities where module Backend.Utilities where
import Annex.Common import Annex.Common
import qualified Annex
import Utility.Hash import Utility.Hash
import Types.Key
import Types.KeySource
import qualified Data.ByteString as S import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy as L
import qualified System.FilePath.ByteString as P
import Data.Char
import Data.Word
{- Generates a keyName from an input string. Takes care of sanitizing it. {- Generates a keyName from an input string. Takes care of sanitizing it.
- If it's not too long, the full string is used as the keyName. - If it's not too long, the full string is used as the keyName.
@ -32,3 +40,34 @@ genKeyName s
sha256len = 64 sha256len = 64
md5len = 32 md5len = 32
{- Converts a key to a version that includes an extension from the
- file that the key was generated from. -}
addE :: KeySource -> (KeyVariety -> KeyVariety) -> Key -> Annex Key
addE source sethasext k = do
maxlen <- annexMaxExtensionLength <$> Annex.getGitConfig
let ext = selectExtension maxlen (keyFilename source)
return $ alterKey k $ \d -> d
{ keyName = keyName d <> ext
, keyVariety = sethasext (keyVariety d)
}
selectExtension :: Maybe Int -> RawFilePath -> S.ByteString
selectExtension maxlen f
| null es = ""
| otherwise = S.intercalate "." ("":es)
where
es = filter (not . S.null) $ reverse $
take 2 $ filter (S.all validInExtension) $
takeWhile shortenough $
reverse $ S.split (fromIntegral (ord '.')) (P.takeExtensions f)
shortenough e = S.length e <= fromMaybe maxExtensionLen maxlen
validInExtension :: Word8 -> Bool
validInExtension c
| isAlphaNum (chr (fromIntegral c)) = True
| fromIntegral c == ord '.' = True
| c <= 127 = False -- other ascii: spaces, punctuation, control chars
| otherwise = True -- utf8 is allowed, also other encodings
maxExtensionLen :: Int
maxExtensionLen = 4 -- long enough for "jpeg"

View file

@ -89,6 +89,10 @@ Here's a list of external backends. Edit this page to add yours to the list.
* [[design/external_backend_protocol/git-annex-backend-XFOO]] * [[design/external_backend_protocol/git-annex-backend-XFOO]]
is a demo program implementing the protocol with a shell script. is a demo program implementing the protocol with a shell script.
Like with git-annex's builtin backends, you can add "E" to the end of the
name of an external backend, to get a version that includes the file
extension in the key.
## notes ## notes
If you want to be able to prove that you're working with the same file If you want to be able to prove that you're working with the same file

View file

@ -154,7 +154,8 @@ The key name is typically some kind of hash, but is not limited to a hash.
The length of it needs to be similar to the lengths of other git-annex The length of it needs to be similar to the lengths of other git-annex
keys. Too long a key name will make it annoying to work with repositories keys. Too long a key name will make it annoying to work with repositories
using them, or even cause problems due to filename length limits. 128 bytes using them, or even cause problems due to filename length limits. 128 bytes
maximum, but shorter is better. maximum, but shorter is better. It should be entirely ascii characters
in the set `A-Za-z0-9` and `-` is allowed, but other punctuation is not.
It's important that, if the program responds with It's important that, if the program responds with
`ISCRYPTOGRAPHICALLYSECURE-YES`, the key name contains only a hash, and not `ISCRYPTOGRAPHICALLYSECURE-YES`, the key name contains only a hash, and not