From 3b967eee6f512fa5eb87399fd829ad6b196897de Mon Sep 17 00:00:00 2001 From: John Ky Date: Tue, 4 Apr 2023 13:45:02 +1000 Subject: [PATCH 1/8] Move OutputFile to cardano-api --- cardano-api/cardano-api.cabal | 3 ++- cardano-api/src/Cardano/Api.hs | 4 ++++ cardano-api/src/Cardano/Api/IO.hs | 8 ++++++++ cardano-cli/src/Cardano/CLI/Shelley/Commands.hs | 5 ----- cardano-cli/src/Cardano/CLI/Shelley/Run/Address.hs | 2 +- cardano-cli/src/Cardano/CLI/Shelley/Run/Address/Info.hs | 1 - 6 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 cardano-api/src/Cardano/Api/IO.hs diff --git a/cardano-api/cardano-api.cabal b/cardano-api/cardano-api.cabal index 65cd169de19..c8ff0fb8f38 100644 --- a/cardano-api/cardano-api.cabal +++ b/cardano-api/cardano-api.cabal @@ -68,9 +68,10 @@ library Cardano.Api.GenesisParameters Cardano.Api.Hash Cardano.Api.HasTypeProxy + Cardano.Api.InMode + Cardano.Api.IO Cardano.Api.IPC Cardano.Api.IPC.Monad - Cardano.Api.InMode Cardano.Api.IPC.Version Cardano.Api.Json Cardano.Api.Keys.Byron diff --git a/cardano-api/src/Cardano/Api.hs b/cardano-api/src/Cardano/Api.hs index 8b117fc6b94..d13f7189c42 100644 --- a/cardano-api/src/Cardano/Api.hs +++ b/cardano-api/src/Cardano/Api.hs @@ -33,6 +33,9 @@ module Cardano.Api ( cardanoEraStyle, shelleyBasedToCardanoEra, + -- ** IO + OutputFile(..), + -- ** Deprecated Byron, Shelley, @@ -800,6 +803,7 @@ import Cardano.Api.GenesisParameters import Cardano.Api.Hash import Cardano.Api.HasTypeProxy import Cardano.Api.InMode +import Cardano.Api.IO import Cardano.Api.IPC import Cardano.Api.IPC.Monad import Cardano.Api.Keys.Byron diff --git a/cardano-api/src/Cardano/Api/IO.hs b/cardano-api/src/Cardano/Api/IO.hs new file mode 100644 index 00000000000..71ddceadf8a --- /dev/null +++ b/cardano-api/src/Cardano/Api/IO.hs @@ -0,0 +1,8 @@ +module Cardano.Api.IO + ( OutputFile(..) + ) where + +newtype OutputFile = OutputFile + { unOutputFile :: FilePath + } + deriving Show diff --git a/cardano-cli/src/Cardano/CLI/Shelley/Commands.hs b/cardano-cli/src/Cardano/CLI/Shelley/Commands.hs index c017a614309..36a8a4f1b4e 100644 --- a/cardano-cli/src/Cardano/CLI/Shelley/Commands.hs +++ b/cardano-cli/src/Cardano/CLI/Shelley/Commands.hs @@ -514,11 +514,6 @@ data MetadataFile = MetadataFileJSON FilePath deriving Show -newtype OutputFile = OutputFile - { unOutputFile :: FilePath - } - deriving Show - newtype PoolMetadataFile = PoolMetadataFile { unPoolMetadataFile :: FilePath } deriving Show diff --git a/cardano-cli/src/Cardano/CLI/Shelley/Run/Address.hs b/cardano-cli/src/Cardano/CLI/Shelley/Run/Address.hs index b7563d11c80..0f0af1633cc 100644 --- a/cardano-cli/src/Cardano/CLI/Shelley/Run/Address.hs +++ b/cardano-cli/src/Cardano/CLI/Shelley/Run/Address.hs @@ -26,7 +26,7 @@ import Cardano.CLI.Shelley.Key (PaymentVerifier (..), StakeIdentifier StakeVerifier (..), VerificationKeyTextOrFile, VerificationKeyTextOrFileError (..), generateKeyPair, readVerificationKeyOrFile, readVerificationKeyTextOrFileAnyOf, renderVerificationKeyTextOrFileError) -import Cardano.CLI.Shelley.Parsers (AddressCmd (..), AddressKeyType (..), OutputFile (..)) +import Cardano.CLI.Shelley.Parsers (AddressCmd (..), AddressKeyType (..)) import Cardano.CLI.Shelley.Run.Address.Info (ShelleyAddressInfoError, runAddressInfo) import Cardano.CLI.Shelley.Run.Read import Cardano.CLI.Types diff --git a/cardano-cli/src/Cardano/CLI/Shelley/Run/Address/Info.hs b/cardano-cli/src/Cardano/CLI/Shelley/Run/Address/Info.hs index 6d1f16ed934..aa98e9f0c4f 100644 --- a/cardano-cli/src/Cardano/CLI/Shelley/Run/Address/Info.hs +++ b/cardano-cli/src/Cardano/CLI/Shelley/Run/Address/Info.hs @@ -5,7 +5,6 @@ module Cardano.CLI.Shelley.Run.Address.Info ) where import Cardano.Api -import Cardano.CLI.Shelley.Parsers (OutputFile (..)) import Control.Monad.IO.Class (MonadIO (..)) import Control.Monad.Trans.Except (ExceptT) From c9fe62bb856f87e3f636190c8e7b00d7db943676 Mon Sep 17 00:00:00 2001 From: John Ky Date: Tue, 4 Apr 2023 13:53:48 +1000 Subject: [PATCH 2/8] New functions for writing to file and stdout: * writeByteStringFile * writeByteStringOutput * writeLazyByteStringFile * writeLazyByteStringOutput * writeTextFile * writeTextOutput --- cardano-api/src/Cardano/Api.hs | 9 +++ cardano-api/src/Cardano/Api/IO.hs | 62 ++++++++++++++++++- .../src/Cardano/Api/SerialiseTextEnvelope.hs | 8 +-- 3 files changed, 73 insertions(+), 6 deletions(-) diff --git a/cardano-api/src/Cardano/Api.hs b/cardano-api/src/Cardano/Api.hs index d13f7189c42..69d94783b8e 100644 --- a/cardano-api/src/Cardano/Api.hs +++ b/cardano-api/src/Cardano/Api.hs @@ -36,6 +36,15 @@ module Cardano.Api ( -- ** IO OutputFile(..), + writeByteStringFile, + writeByteStringOutput, + + writeLazyByteStringFile, + writeLazyByteStringOutput, + + writeTextFile, + writeTextOutput, + -- ** Deprecated Byron, Shelley, diff --git a/cardano-api/src/Cardano/Api/IO.hs b/cardano-api/src/Cardano/Api/IO.hs index 71ddceadf8a..9db103b1c45 100644 --- a/cardano-api/src/Cardano/Api/IO.hs +++ b/cardano-api/src/Cardano/Api/IO.hs @@ -1,8 +1,68 @@ +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + module Cardano.Api.IO ( OutputFile(..) + + , writeByteStringFile + , writeByteStringOutput + + , writeLazyByteStringFile + , writeLazyByteStringOutput + + , writeTextFile + , writeTextOutput ) where +import Cardano.Api.Error (FileError (..)) + +import Control.Monad.Except (runExceptT) +import Control.Monad.IO.Class (MonadIO (..)) +import Control.Monad.Trans.Except.Extra (handleIOExceptT) +import Data.Aeson.Types (FromJSON, ToJSON) +import Data.ByteString (ByteString) +import qualified Data.ByteString.Char8 as BS +import qualified Data.ByteString.Char8 as BSC +import qualified Data.ByteString.Lazy as LBS +import qualified Data.ByteString.Lazy as LBSC +import Data.String (IsString) +import Data.Text (Text) +import qualified Data.Text.IO as Text +import GHC.Generics (Generic) + newtype OutputFile = OutputFile { unOutputFile :: FilePath } - deriving Show + deriving Generic + deriving newtype (Eq, Ord, Show, IsString, ToJSON, FromJSON) + +writeByteStringFile :: MonadIO m => FilePath -> ByteString -> m (Either (FileError ()) ()) +writeByteStringFile fp bs = runExceptT $ + handleIOExceptT (FileIOError fp) $ BS.writeFile fp bs + +writeByteStringOutput :: MonadIO m => Maybe FilePath -> ByteString -> m (Either (FileError ()) ()) +writeByteStringOutput mOutput bs = runExceptT $ + case mOutput of + Just fp -> handleIOExceptT (FileIOError fp) $ BS.writeFile fp bs + Nothing -> liftIO $ BSC.putStr bs + +writeLazyByteStringFile :: MonadIO m => FilePath -> LBS.ByteString -> m (Either (FileError ()) ()) +writeLazyByteStringFile fp bs = runExceptT $ + handleIOExceptT (FileIOError fp) $ LBS.writeFile fp bs + +writeLazyByteStringOutput :: MonadIO m => Maybe FilePath -> LBS.ByteString -> m (Either (FileError ()) ()) +writeLazyByteStringOutput mOutput bs = runExceptT $ + case mOutput of + Just fp -> handleIOExceptT (FileIOError fp) $ LBS.writeFile fp bs + Nothing -> liftIO $ LBSC.putStr bs + +writeTextFile :: MonadIO m => FilePath -> Text -> m (Either (FileError ()) ()) +writeTextFile fp t = runExceptT $ + handleIOExceptT (FileIOError fp) $ Text.writeFile fp t + +writeTextOutput :: MonadIO m => Maybe FilePath -> Text -> m (Either (FileError ()) ()) +writeTextOutput mOutput t = runExceptT $ + case mOutput of + Just fp -> handleIOExceptT (FileIOError fp) $ Text.writeFile fp t + Nothing -> liftIO $ Text.putStr t diff --git a/cardano-api/src/Cardano/Api/SerialiseTextEnvelope.hs b/cardano-api/src/Cardano/Api/SerialiseTextEnvelope.hs index 693e55551d9..d3d3af1eded 100644 --- a/cardano-api/src/Cardano/Api/SerialiseTextEnvelope.hs +++ b/cardano-api/src/Cardano/Api/SerialiseTextEnvelope.hs @@ -60,6 +60,7 @@ import Cardano.Binary (DecoderError) import Cardano.Api.Error import Cardano.Api.HasTypeProxy +import Cardano.Api.IO (writeLazyByteStringFile) import Cardano.Api.SerialiseCBOR import Cardano.Api.Utils (readFileBlocking) @@ -276,11 +277,8 @@ writeFileTextEnvelope :: HasTextEnvelope a -> Maybe TextEnvelopeDescr -> a -> IO (Either (FileError ()) ()) -writeFileTextEnvelope path mbDescr a = - runExceptT $ do - handleIOExceptT (FileIOError path) $ LBS.writeFile path content - where - content = textEnvelopeToJSON mbDescr a +writeFileTextEnvelope outputFile mbDescr a = + writeLazyByteStringFile outputFile (textEnvelopeToJSON mbDescr a) writeFileTextEnvelopeWithOwnerPermissions From 2e5adc2e5556e29f30cc75b31e6e43bf36e9cd47 Mon Sep 17 00:00:00 2001 From: John Ky Date: Tue, 4 Apr 2023 15:45:51 +1000 Subject: [PATCH 3/8] Split callsite of writeFileTextEnvelope to separate serialisation from IO --- .../src/Cardano/CLI/Shelley/Run/Address.hs | 4 +-- .../src/Cardano/CLI/Shelley/Run/Genesis.hs | 33 ++++++++++++------- .../src/Cardano/CLI/Shelley/Run/Governance.hs | 11 ++++--- .../src/Cardano/CLI/Shelley/Run/Key.hs | 32 ++++++++++-------- .../src/Cardano/CLI/Shelley/Run/Node.hs | 32 ++++++++++-------- .../src/Cardano/CLI/Shelley/Run/Pool.hs | 6 ++-- .../Cardano/CLI/Shelley/Run/StakeAddress.hs | 13 +++++--- .../Cardano/CLI/Shelley/Run/Transaction.hs | 11 ++++--- 8 files changed, 86 insertions(+), 56 deletions(-) diff --git a/cardano-cli/src/Cardano/CLI/Shelley/Run/Address.hs b/cardano-cli/src/Cardano/CLI/Shelley/Run/Address.hs index 0f0af1633cc..c42790cecc4 100644 --- a/cardano-cli/src/Cardano/CLI/Shelley/Run/Address.hs +++ b/cardano-cli/src/Cardano/CLI/Shelley/Run/Address.hs @@ -91,8 +91,8 @@ writePaymentKeyFiles -> ExceptT ShelleyAddressCmdError IO () writePaymentKeyFiles (VerificationKeyFile vkeyPath) (SigningKeyFile skeyPath) vkey skey = do firstExceptT ShelleyAddressCmdWriteFileError $ do - newExceptT $ writeFileTextEnvelope skeyPath (Just skeyDesc) skey - newExceptT $ writeFileTextEnvelope vkeyPath (Just vkeyDesc) vkey + newExceptT $ writeLazyByteStringFile skeyPath $ textEnvelopeToJSON (Just skeyDesc) skey + newExceptT $ writeLazyByteStringFile vkeyPath $ textEnvelopeToJSON (Just vkeyDesc) vkey where skeyDesc, vkeyDesc :: TextEnvelopeDescr skeyDesc = "Payment Signing Key" diff --git a/cardano-cli/src/Cardano/CLI/Shelley/Run/Genesis.hs b/cardano-cli/src/Cardano/CLI/Shelley/Run/Genesis.hs index ab54da7f573..5c21c4204a3 100644 --- a/cardano-cli/src/Cardano/CLI/Shelley/Run/Genesis.hs +++ b/cardano-cli/src/Cardano/CLI/Shelley/Run/Genesis.hs @@ -238,10 +238,12 @@ runGenesisKeyGenGenesis (VerificationKeyFile vkeyPath) let vkey = getVerificationKey skey firstExceptT ShelleyGenesisCmdGenesisFileError . newExceptT - $ writeFileTextEnvelope skeyPath (Just skeyDesc) skey + $ writeLazyByteStringFile skeyPath + $ textEnvelopeToJSON (Just skeyDesc) skey firstExceptT ShelleyGenesisCmdGenesisFileError . newExceptT - $ writeFileTextEnvelope vkeyPath (Just vkeyDesc) vkey + $ writeLazyByteStringFile vkeyPath + $ textEnvelopeToJSON (Just vkeyDesc) vkey where skeyDesc, vkeyDesc :: TextEnvelopeDescr skeyDesc = "Genesis Signing Key" @@ -259,13 +261,16 @@ runGenesisKeyGenDelegate (VerificationKeyFile vkeyPath) let vkey = getVerificationKey skey firstExceptT ShelleyGenesisCmdGenesisFileError . newExceptT - $ writeFileTextEnvelope skeyPath (Just skeyDesc) skey + $ writeLazyByteStringFile skeyPath + $ textEnvelopeToJSON (Just skeyDesc) skey firstExceptT ShelleyGenesisCmdGenesisFileError . newExceptT - $ writeFileTextEnvelope vkeyPath (Just vkeyDesc) vkey + $ writeLazyByteStringFile vkeyPath + $ textEnvelopeToJSON (Just vkeyDesc) vkey firstExceptT ShelleyGenesisCmdGenesisFileError . newExceptT - $ writeFileTextEnvelope ocertCtrPath (Just certCtrDesc) + $ writeLazyByteStringFile ocertCtrPath + $ textEnvelopeToJSON (Just certCtrDesc) $ OperationalCertificateIssueCounter initialCounter (castVerificationKey vkey) -- Cast to a 'StakePoolKey' @@ -288,10 +293,12 @@ runGenesisKeyGenDelegateVRF (VerificationKeyFile vkeyPath) let vkey = getVerificationKey skey firstExceptT ShelleyGenesisCmdGenesisFileError . newExceptT - $ writeFileTextEnvelope skeyPath (Just skeyDesc) skey + $ writeLazyByteStringFile skeyPath + $ textEnvelopeToJSON (Just skeyDesc) skey firstExceptT ShelleyGenesisCmdGenesisFileError . newExceptT - $ writeFileTextEnvelope vkeyPath (Just vkeyDesc) vkey + $ writeLazyByteStringFile vkeyPath + $ textEnvelopeToJSON (Just vkeyDesc) vkey where skeyDesc, vkeyDesc :: TextEnvelopeDescr skeyDesc = "VRF Signing Key" @@ -306,10 +313,12 @@ runGenesisKeyGenUTxO (VerificationKeyFile vkeyPath) let vkey = getVerificationKey skey firstExceptT ShelleyGenesisCmdGenesisFileError . newExceptT - $ writeFileTextEnvelope skeyPath (Just skeyDesc) skey + $ writeLazyByteStringFile skeyPath + $ textEnvelopeToJSON (Just skeyDesc) skey firstExceptT ShelleyGenesisCmdGenesisFileError . newExceptT - $ writeFileTextEnvelope vkeyPath (Just vkeyDesc) vkey + $ writeLazyByteStringFile vkeyPath + $ textEnvelopeToJSON (Just vkeyDesc) vkey where skeyDesc, vkeyDesc :: TextEnvelopeDescr skeyDesc = "Genesis Initial UTxO Signing Key" @@ -362,9 +371,9 @@ runGenesisVerKey (VerificationKeyFile vkeyPath) (SigningKeyFile skeyPath) = do firstExceptT ShelleyGenesisCmdGenesisFileError . newExceptT . liftIO $ case vkey of - AGenesisKey vk -> writeFileTextEnvelope vkeyPath Nothing vk - AGenesisDelegateKey vk -> writeFileTextEnvelope vkeyPath Nothing vk - AGenesisUTxOKey vk -> writeFileTextEnvelope vkeyPath Nothing vk + AGenesisKey vk -> writeLazyByteStringFile vkeyPath $ textEnvelopeToJSON Nothing vk + AGenesisDelegateKey vk -> writeLazyByteStringFile vkeyPath $ textEnvelopeToJSON Nothing vk + AGenesisUTxOKey vk -> writeLazyByteStringFile vkeyPath $ textEnvelopeToJSON Nothing vk data SomeGenesisKey f = AGenesisKey (f GenesisKey) diff --git a/cardano-cli/src/Cardano/CLI/Shelley/Run/Governance.hs b/cardano-cli/src/Cardano/CLI/Shelley/Run/Governance.hs index beb79fb7847..7951de93159 100644 --- a/cardano-cli/src/Cardano/CLI/Shelley/Run/Governance.hs +++ b/cardano-cli/src/Cardano/CLI/Shelley/Run/Governance.hs @@ -92,7 +92,7 @@ runGovernanceMIRCertificatePayStakeAddrs mirPot sAddrs rwdAmts (OutputFile oFp) firstExceptT ShelleyGovernanceCmdTextEnvWriteError . newExceptT - $ writeFileTextEnvelope oFp (Just mirCertDesc) mirCert + $ writeLazyByteStringFile oFp $ textEnvelopeToJSON (Just mirCertDesc) mirCert where mirCertDesc :: TextEnvelopeDescr mirCertDesc = "Move Instantaneous Rewards Certificate" @@ -111,7 +111,8 @@ runGovernanceMIRCertificateTransfer ll (OutputFile oFp) direction = do firstExceptT ShelleyGovernanceCmdTextEnvWriteError . newExceptT - $ writeFileTextEnvelope oFp (Just $ mirCertDesc direction) mirCert + $ writeLazyByteStringFile oFp + $ textEnvelopeToJSON (Just $ mirCertDesc direction) mirCert where mirCertDesc :: TransferDirection -> TextEnvelopeDescr mirCertDesc TransferToTreasury = "MIR Certificate Send To Treasury" @@ -139,7 +140,8 @@ runGovernanceGenesisKeyDelegationCertificate genVkOrHashOrFp $ readVerificationKeyOrHashOrFile AsVrfKey vrfVkOrHashOrFp firstExceptT ShelleyGovernanceCmdTextEnvWriteError . newExceptT - $ writeFileTextEnvelope oFp (Just genKeyDelegCertDesc) + $ writeLazyByteStringFile oFp + $ textEnvelopeToJSON (Just genKeyDelegCertDesc) $ makeGenesisKeyDelegationCertificate genesisVkHash genesisDelVkHash vrfVkHash where genKeyDelegCertDesc :: TextEnvelopeDescr @@ -175,5 +177,6 @@ runGovernanceUpdateProposal (OutputFile upFile) eNo genVerKeyFiles upPprams mCos let genKeyHashes = fmap verificationKeyHash genVKeys upProp = makeShelleyUpdateProposal finalUpPprams genKeyHashes eNo - firstExceptT ShelleyGovernanceCmdTextEnvWriteError . newExceptT $ writeFileTextEnvelope upFile Nothing upProp + firstExceptT ShelleyGovernanceCmdTextEnvWriteError . newExceptT + $ writeLazyByteStringFile upFile $ textEnvelopeToJSON Nothing upProp diff --git a/cardano-cli/src/Cardano/CLI/Shelley/Run/Key.hs b/cardano-cli/src/Cardano/CLI/Shelley/Run/Key.hs index a053bd6ae51..7fd333ef7f1 100644 --- a/cardano-cli/src/Cardano/CLI/Shelley/Run/Key.hs +++ b/cardano-cli/src/Cardano/CLI/Shelley/Run/Key.hs @@ -120,7 +120,7 @@ runGetVerificationKey skf (VerificationKeyFile vkf) = do withSomeSigningKey ssk $ \sk -> let vk = getVerificationKey sk in firstExceptT ShelleyKeyCmdWriteFileError . newExceptT $ - writeFileTextEnvelope vkf Nothing vk + writeLazyByteStringFile vkf $ textEnvelopeToJSON Nothing vk data SomeSigningKey @@ -244,8 +244,9 @@ runConvertToNonExtendedKey evkf (VerificationKeyFile vkf) = writeToDisk :: Key keyrole => FilePath -> VerificationKey keyrole -> ExceptT ShelleyKeyCmdError IO () - writeToDisk vkf' vk = firstExceptT ShelleyKeyCmdWriteFileError . newExceptT - $ writeFileTextEnvelope vkf' Nothing vk + writeToDisk vkf' vk = + firstExceptT ShelleyKeyCmdWriteFileError . newExceptT + $ writeLazyByteStringFile vkf' $ textEnvelopeToJSON Nothing vk readExtendedVerificationKeyFile @@ -364,7 +365,7 @@ convertByronSigningKey mPwd byronFormat convert sk' = convert unprotectedSk firstExceptT ShelleyKeyCmdWriteFileError . newExceptT $ - writeFileTextEnvelope skeyPathNew Nothing sk' + writeLazyByteStringFile skeyPathNew $ textEnvelopeToJSON Nothing sk' convertByronVerificationKey :: forall keyrole. @@ -384,7 +385,7 @@ convertByronVerificationKey convert vk' = convert vk firstExceptT ShelleyKeyCmdWriteFileError . newExceptT $ - writeFileTextEnvelope vkeyPathNew Nothing vk' + writeLazyByteStringFile vkeyPathNew $ textEnvelopeToJSON Nothing vk' runConvertByronGenesisVerificationKey @@ -404,7 +405,7 @@ runConvertByronGenesisVerificationKey (VerificationKeyBase64 b64ByronVKey) vk' = convert vk firstExceptT ShelleyKeyCmdWriteFileError . newExceptT $ - writeFileTextEnvelope vkeyPathNew Nothing vk' + writeLazyByteStringFile vkeyPathNew $ textEnvelopeToJSON Nothing vk' where convert :: Byron.VerificationKey -> VerificationKey GenesisKey convert (Byron.VerificationKey xvk) = @@ -426,7 +427,7 @@ runConvertITNStakeKey (AVerificationKeyFile (VerificationKeyFile vk)) (OutputFil . first ShelleyKeyCmdItnKeyConvError $ convertITNVerificationKey bech32publicKey firstExceptT ShelleyKeyCmdWriteFileError . newExceptT $ - writeFileTextEnvelope outFile Nothing vkey + writeLazyByteStringFile outFile $ textEnvelopeToJSON Nothing vkey runConvertITNStakeKey (ASigningKeyFile (SigningKeyFile sk)) (OutputFile outFile) = do bech32privateKey <- firstExceptT ShelleyKeyCmdItnKeyConvError . newExceptT $ @@ -434,8 +435,9 @@ runConvertITNStakeKey (ASigningKeyFile (SigningKeyFile sk)) (OutputFile outFile) skey <- hoistEither . first ShelleyKeyCmdItnKeyConvError $ convertITNSigningKey bech32privateKey - firstExceptT ShelleyKeyCmdWriteFileError . newExceptT $ - writeFileTextEnvelope outFile Nothing skey + firstExceptT ShelleyKeyCmdWriteFileError . newExceptT + $ writeLazyByteStringFile outFile + $ textEnvelopeToJSON Nothing skey runConvertITNExtendedToStakeKey :: SomeKeyFile -> OutputFile -> ExceptT ShelleyKeyCmdError IO () runConvertITNExtendedToStakeKey (AVerificationKeyFile _) _ = left ShelleyKeyCmdWrongKeyTypeError @@ -444,7 +446,8 @@ runConvertITNExtendedToStakeKey (ASigningKeyFile (SigningKeyFile sk)) (OutputFil skey <- hoistEither . first ShelleyKeyCmdItnKeyConvError $ convertITNExtendedSigningKey bech32privateKey firstExceptT ShelleyKeyCmdWriteFileError . newExceptT - $ writeFileTextEnvelope outFile Nothing skey + $ writeLazyByteStringFile outFile + $ textEnvelopeToJSON Nothing skey runConvertITNBip32ToStakeKey :: SomeKeyFile -> OutputFile -> ExceptT ShelleyKeyCmdError IO () runConvertITNBip32ToStakeKey (AVerificationKeyFile _) _ = left ShelleyKeyCmdWrongKeyTypeError @@ -453,7 +456,8 @@ runConvertITNBip32ToStakeKey (ASigningKeyFile (SigningKeyFile sk)) (OutputFile o skey <- hoistEither . first ShelleyKeyCmdItnKeyConvError $ convertITNBIP32SigningKey bech32privateKey firstExceptT ShelleyKeyCmdWriteFileError . newExceptT - $ writeFileTextEnvelope outFile Nothing skey + $ writeLazyByteStringFile outFile + $ textEnvelopeToJSON Nothing skey -- | An error that can occur while converting an Incentivized Testnet (ITN) -- key. @@ -649,8 +653,8 @@ writeSomeCardanoAddressSigningKeyFile writeSomeCardanoAddressSigningKeyFile outFile skey = case skey of ACardanoAddrShelleyPaymentSigningKey sk -> - writeFileTextEnvelope outFile Nothing sk + writeLazyByteStringFile outFile $ textEnvelopeToJSON Nothing sk ACardanoAddrShelleyStakeSigningKey sk -> - writeFileTextEnvelope outFile Nothing sk + writeLazyByteStringFile outFile $ textEnvelopeToJSON Nothing sk ACardanoAddrByronSigningKey sk -> - writeFileTextEnvelope outFile Nothing sk + writeLazyByteStringFile outFile $ textEnvelopeToJSON Nothing sk diff --git a/cardano-cli/src/Cardano/CLI/Shelley/Run/Node.hs b/cardano-cli/src/Cardano/CLI/Shelley/Run/Node.hs index 3319f0dd61c..b146baf2b80 100644 --- a/cardano-cli/src/Cardano/CLI/Shelley/Run/Node.hs +++ b/cardano-cli/src/Cardano/CLI/Shelley/Run/Node.hs @@ -81,13 +81,16 @@ runNodeKeyGenCold (VerificationKeyFile vkeyPath) (SigningKeyFile skeyPath) let vkey = getVerificationKey skey firstExceptT ShelleyNodeCmdWriteFileError . newExceptT - $ writeFileTextEnvelope skeyPath (Just skeyDesc) skey + $ writeLazyByteStringFile skeyPath + $ textEnvelopeToJSON (Just skeyDesc) skey firstExceptT ShelleyNodeCmdWriteFileError . newExceptT - $ writeFileTextEnvelope vkeyPath (Just vkeyDesc) vkey + $ writeLazyByteStringFile vkeyPath + $ textEnvelopeToJSON (Just vkeyDesc) vkey firstExceptT ShelleyNodeCmdWriteFileError . newExceptT - $ writeFileTextEnvelope ocertCtrPath (Just ocertCtrDesc) + $ writeLazyByteStringFile ocertCtrPath + $ textEnvelopeToJSON (Just ocertCtrDesc) $ OperationalCertificateIssueCounter initialCounter vkey where skeyDesc, vkeyDesc, ocertCtrDesc :: TextEnvelopeDescr @@ -108,10 +111,12 @@ runNodeKeyGenKES (VerificationKeyFile vkeyPath) (SigningKeyFile skeyPath) = do let vkey = getVerificationKey skey firstExceptT ShelleyNodeCmdWriteFileError . newExceptT - $ writeFileTextEnvelope skeyPath (Just skeyDesc) skey + $ writeLazyByteStringFile skeyPath + $ textEnvelopeToJSON (Just skeyDesc) skey firstExceptT ShelleyNodeCmdWriteFileError . newExceptT - $ writeFileTextEnvelope vkeyPath (Just vkeyDesc) vkey + $ writeLazyByteStringFile vkeyPath + $ textEnvelopeToJSON (Just vkeyDesc) vkey where skeyDesc, vkeyDesc :: TextEnvelopeDescr skeyDesc = "KES Signing Key" @@ -127,7 +132,8 @@ runNodeKeyGenVRF (VerificationKeyFile vkeyPath) (SigningKeyFile skeyPath) = do $ writeFileTextEnvelopeWithOwnerPermissions skeyPath (Just skeyDesc) skey firstExceptT ShelleyNodeCmdWriteFileError . newExceptT - $ writeFileTextEnvelope vkeyPath (Just vkeyDesc) vkey + $ writeLazyByteStringFile vkeyPath + $ textEnvelopeToJSON (Just vkeyDesc) vkey where skeyDesc, vkeyDesc :: TextEnvelopeDescr skeyDesc = "VRF Signing Key" @@ -161,8 +167,9 @@ runNodeNewCounter coldVerKeyOrFile counter let ocertIssueCounter = OperationalCertificateIssueCounter (fromIntegral counter) vkey - firstExceptT ShelleyNodeCmdWriteFileError . newExceptT $ - writeFileTextEnvelope ocertCtrPath Nothing ocertIssueCounter + firstExceptT ShelleyNodeCmdWriteFileError . newExceptT + $ writeLazyByteStringFile ocertCtrPath + $ textEnvelopeToJSON Nothing ocertIssueCounter runNodeIssueOpCert :: VerificationKeyOrFile KesKey @@ -210,14 +217,13 @@ runNodeIssueOpCert kesVerKeyOrFile -- a new cert but without updating the counter. firstExceptT ShelleyNodeCmdWriteFileError . newExceptT - $ writeFileTextEnvelope - ocertCtrPath - (Just $ ocertCtrDesc $ getCounter nextOcertCtr) - nextOcertCtr + $ writeLazyByteStringFile ocertCtrPath + $ textEnvelopeToJSON (Just $ ocertCtrDesc $ getCounter nextOcertCtr) nextOcertCtr firstExceptT ShelleyNodeCmdWriteFileError . newExceptT - $ writeFileTextEnvelope certFile Nothing ocert + $ writeLazyByteStringFile certFile + $ textEnvelopeToJSON Nothing ocert where getCounter :: OperationalCertificateIssueCounter -> Word64 getCounter (OperationalCertificateIssueCounter n _) = n diff --git a/cardano-cli/src/Cardano/CLI/Shelley/Run/Pool.hs b/cardano-cli/src/Cardano/CLI/Shelley/Run/Pool.hs index 04793003a25..19530df820a 100644 --- a/cardano-cli/src/Cardano/CLI/Shelley/Run/Pool.hs +++ b/cardano-cli/src/Cardano/CLI/Shelley/Run/Pool.hs @@ -135,7 +135,8 @@ runStakePoolRegistrationCert firstExceptT ShelleyPoolCmdWriteFileError . newExceptT - $ writeFileTextEnvelope outfp (Just registrationCertDesc) registrationCert + $ writeLazyByteStringFile outfp + $ textEnvelopeToJSON (Just registrationCertDesc) registrationCert where registrationCertDesc :: TextEnvelopeDescr registrationCertDesc = "Stake Pool Registration Certificate" @@ -156,7 +157,8 @@ runStakePoolRetirementCert stakePoolVerKeyOrFile retireEpoch (OutputFile outfp) firstExceptT ShelleyPoolCmdWriteFileError . newExceptT - $ writeFileTextEnvelope outfp (Just retireCertDesc) retireCert + $ writeLazyByteStringFile outfp + $ textEnvelopeToJSON (Just retireCertDesc) retireCert where retireCertDesc :: TextEnvelopeDescr retireCertDesc = "Stake Pool Retirement Certificate" diff --git a/cardano-cli/src/Cardano/CLI/Shelley/Run/StakeAddress.hs b/cardano-cli/src/Cardano/CLI/Shelley/Run/StakeAddress.hs index 9529c40285a..8254871371a 100644 --- a/cardano-cli/src/Cardano/CLI/Shelley/Run/StakeAddress.hs +++ b/cardano-cli/src/Cardano/CLI/Shelley/Run/StakeAddress.hs @@ -68,8 +68,8 @@ runStakeAddressKeyGenToFile (VerificationKeyFile vkFp) (SigningKeyFile skFp) = d let vkey = getVerificationKey skey firstExceptT ShelleyStakeAddressCmdWriteFileError $ do - newExceptT $ writeFileTextEnvelope skFp (Just skeyDesc) skey - newExceptT $ writeFileTextEnvelope vkFp (Just vkeyDesc) vkey + newExceptT $ writeLazyByteStringFile skFp $ textEnvelopeToJSON (Just skeyDesc) skey + newExceptT $ writeLazyByteStringFile vkFp $ textEnvelopeToJSON (Just vkeyDesc) vkey runStakeAddressKeyHash :: VerificationKeyOrFile StakeKey @@ -116,7 +116,8 @@ runStakeCredentialRegistrationCert stakeIdentifier (OutputFile oFp) = do let deRegCert = makeStakeAddressRegistrationCertificate sCred firstExceptT ShelleyStakeAddressCmdWriteFileError . newExceptT - $ writeFileTextEnvelope oFp (Just regCertDesc) deRegCert + $ writeLazyByteStringFile oFp + $ textEnvelopeToJSON (Just regCertDesc) deRegCert regCertDesc :: TextEnvelopeDescr regCertDesc = "Stake Address Registration Certificate" @@ -147,7 +148,8 @@ runStakeCredentialDelegationCert stakeVerifier poolVKeyOrHashOrFile (OutputFile let delegCert = makeStakeAddressDelegationCertificate sCred poolStakeVKeyHash firstExceptT ShelleyStakeAddressCmdWriteFileError . newExceptT - $ writeFileTextEnvelope outFp (Just delegCertDesc) delegCert + $ writeLazyByteStringFile outFp + $ textEnvelopeToJSON (Just delegCertDesc) delegCert delegCertDesc :: TextEnvelopeDescr delegCertDesc = "Stake Address Delegation Certificate" @@ -169,7 +171,8 @@ runStakeCredentialDeRegistrationCert stakeVerifier (OutputFile oFp) = do let deRegCert = makeStakeAddressDeregistrationCertificate sCred firstExceptT ShelleyStakeAddressCmdWriteFileError . newExceptT - $ writeFileTextEnvelope oFp (Just deregCertDesc) deRegCert + $ writeLazyByteStringFile oFp + $ textEnvelopeToJSON (Just deregCertDesc) deRegCert deregCertDesc :: TextEnvelopeDescr deregCertDesc = "Stake Address Deregistration Certificate" diff --git a/cardano-cli/src/Cardano/CLI/Shelley/Run/Transaction.hs b/cardano-cli/src/Cardano/CLI/Shelley/Run/Transaction.hs index ac5f3ecf220..9f823ed00b1 100644 --- a/cardano-cli/src/Cardano/CLI/Shelley/Run/Transaction.hs +++ b/cardano-cli/src/Cardano/CLI/Shelley/Run/Transaction.hs @@ -1102,8 +1102,9 @@ runTxSign txOrTxBody witSigningData mnw (TxFile outTxFile) = do let shelleyKeyWitnesses = map (makeShelleyKeyWitness txbody) sksShelley tx = makeSignedTransaction (byronWitnesses ++ shelleyKeyWitnesses) txbody - firstExceptT ShelleyTxCmdWriteFileError . newExceptT $ - writeFileTextEnvelope outTxFile Nothing tx + firstExceptT ShelleyTxCmdWriteFileError . newExceptT + $ writeLazyByteStringFile outTxFile + $ textEnvelopeToJSON Nothing tx -- ---------------------------------------------------------------------------- -- Transaction submission @@ -1392,7 +1393,8 @@ runTxCreateWitness (TxBodyFile txbodyFilePath) witSignData mbNw (OutputFile oFil pure $ makeShelleyKeyWitness txbody skShelley firstExceptT ShelleyTxCmdWriteFileError . newExceptT - $ writeFileTextEnvelope oFile Nothing witness + $ writeLazyByteStringFile oFile + $ textEnvelopeToJSON Nothing witness runTxSignWitness :: TxBodyFile @@ -1421,7 +1423,8 @@ runTxSignWitness (TxBodyFile txbodyFilePath) witnessFiles (OutputFile oFp) = do let tx = makeSignedTransaction witnesses txbody firstExceptT ShelleyTxCmdWriteFileError . newExceptT - $ writeFileTextEnvelope oFp Nothing tx + $ writeLazyByteStringFile oFp + $ textEnvelopeToJSON Nothing tx IncompleteCddlFormattedTx (InAnyCardanoEra era anyTx) -> do let txbody = getTxBody anyTx From 15a843d4436971f705d492dc3a7953827165eaa8 Mon Sep 17 00:00:00 2001 From: John Ky Date: Tue, 4 Apr 2023 19:02:45 +1000 Subject: [PATCH 4/8] Move writeFileWithOwnerPermissions to Cardano.Api.IO.Compat so that Cardano.Api.SerialiseTextEnvelope has less IO code and can be freed from CPP --- cardano-api/cardano-api.cabal | 1 + cardano-api/src/Cardano/Api/IO.hs | 3 + cardano-api/src/Cardano/Api/IO/Compat.hs | 75 +++++++++++++++++++ .../src/Cardano/Api/SerialiseTextEnvelope.hs | 71 +----------------- 4 files changed, 81 insertions(+), 69 deletions(-) create mode 100644 cardano-api/src/Cardano/Api/IO/Compat.hs diff --git a/cardano-api/cardano-api.cabal b/cardano-api/cardano-api.cabal index c8ff0fb8f38..bb95bc92837 100644 --- a/cardano-api/cardano-api.cabal +++ b/cardano-api/cardano-api.cabal @@ -70,6 +70,7 @@ library Cardano.Api.HasTypeProxy Cardano.Api.InMode Cardano.Api.IO + Cardano.Api.IO.Compat Cardano.Api.IPC Cardano.Api.IPC.Monad Cardano.Api.IPC.Version diff --git a/cardano-api/src/Cardano/Api/IO.hs b/cardano-api/src/Cardano/Api/IO.hs index 9db103b1c45..6bcb768cbb5 100644 --- a/cardano-api/src/Cardano/Api/IO.hs +++ b/cardano-api/src/Cardano/Api/IO.hs @@ -13,9 +13,12 @@ module Cardano.Api.IO , writeTextFile , writeTextOutput + + , writeFileWithOwnerPermissions ) where import Cardano.Api.Error (FileError (..)) +import Cardano.Api.IO.Compat (writeFileWithOwnerPermissions) import Control.Monad.Except (runExceptT) import Control.Monad.IO.Class (MonadIO (..)) diff --git a/cardano-api/src/Cardano/Api/IO/Compat.hs b/cardano-api/src/Cardano/Api/IO/Compat.hs new file mode 100644 index 00000000000..c719966165f --- /dev/null +++ b/cardano-api/src/Cardano/Api/IO/Compat.hs @@ -0,0 +1,75 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Api.IO.Compat + ( writeFileWithOwnerPermissions + ) where + +#if !defined(mingw32_HOST_OS) +#define UNIX +#endif + +import Cardano.Api.Error (FileError (..)) + +import Control.Monad.Except (runExceptT) +import Control.Monad.Trans.Except.Extra (handleIOExceptT) +import qualified Data.ByteString.Lazy as LBS + +#ifdef UNIX +import Control.Exception (IOException, bracket, bracketOnError, try) +import System.Directory () +import System.IO (hClose) +import System.Posix.Files (ownerModes, setFdOwnerAndGroup) +import System.Posix.IO (OpenMode (..), closeFd, defaultFileFlags, fdToHandle, openFd) +import System.Posix.User (getRealUserID) +#else +import Control.Exception (bracketOnError) +import System.Directory (removeFile, renameFile) +import System.FilePath (splitFileName, (<.>)) +import System.IO (hClose, openTempFile) +#endif + +writeFileWithOwnerPermissions + :: FilePath + -> LBS.ByteString + -> IO (Either (FileError ()) ()) +#ifdef UNIX +-- On a unix based system, we grab a file descriptor and set ourselves as owner. +-- Since we're holding the file descriptor at this point, we can be sure that +-- what we're about to write to is owned by us if an error didn't occur. +writeFileWithOwnerPermissions path a = do + user <- getRealUserID + ownedFile <- try $ + -- We only close the FD on error here, otherwise we let it leak out, since + -- it will be immediately turned into a Handle (which will be closed when + -- the Handle is closed) + bracketOnError + (openFd path WriteOnly (Just ownerModes) defaultFileFlags) + closeFd + (\fd -> setFdOwnerAndGroup fd user (-1) >> pure fd) + case ownedFile of + Left (err :: IOException) -> do + pure $ Left $ FileIOError path err + Right fd -> do + bracket + (fdToHandle fd) + hClose + (\handle -> runExceptT $ handleIOExceptT (FileIOError path) $ LBS.hPut handle a) +#else +-- On something other than unix, we make a _new_ file, and since we created it, +-- we must own it. We then place it at the target location. Unfortunately this +-- won't work correctly with pseudo-files. +writeFileWithOwnerPermissions targetPath a = + bracketOnError + (openTempFile targetDir $ targetFile <.> "tmp") + (\(tmpPath, fHandle) -> do + hClose fHandle >> removeFile tmpPath + return . Left $ FileErrorTempFile targetPath tmpPath fHandle) + (\(tmpPath, fHandle) -> do + LBS.hPut fHandle a + hClose fHandle + renameFile tmpPath targetPath + return $ Right ()) + where + (targetDir, targetFile) = splitFileName targetPath +#endif diff --git a/cardano-api/src/Cardano/Api/SerialiseTextEnvelope.hs b/cardano-api/src/Cardano/Api/SerialiseTextEnvelope.hs index d3d3af1eded..7c52c33a676 100644 --- a/cardano-api/src/Cardano/Api/SerialiseTextEnvelope.hs +++ b/cardano-api/src/Cardano/Api/SerialiseTextEnvelope.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE CPP #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} @@ -6,10 +5,6 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} -#if !defined(mingw32_HOST_OS) -#define UNIX -#endif - -- | TextEnvelope Serialisation -- module Cardano.Api.SerialiseTextEnvelope @@ -60,25 +55,10 @@ import Cardano.Binary (DecoderError) import Cardano.Api.Error import Cardano.Api.HasTypeProxy -import Cardano.Api.IO (writeLazyByteStringFile) +import Cardano.Api.IO (writeFileWithOwnerPermissions, writeLazyByteStringFile) import Cardano.Api.SerialiseCBOR import Cardano.Api.Utils (readFileBlocking) -#ifdef UNIX -import Control.Exception (IOException, bracket, bracketOnError, try) -import System.Directory () -import System.IO (hClose) -import System.Posix.Files (ownerModes, setFdOwnerAndGroup) -import System.Posix.IO (OpenMode (..), closeFd, defaultFileFlags, fdToHandle, openFd) -import System.Posix.User (getRealUserID) -#else -import Control.Exception (bracketOnError) -import System.Directory (removeFile, renameFile) -import System.FilePath (splitFileName, (<.>)) -import System.IO (hClose, openTempFile) -#endif - - -- ---------------------------------------------------------------------------- -- Text envelopes -- @@ -227,51 +207,6 @@ deserialiseFromTextEnvelopeAnyOf types te = matching (FromSomeType ttoken _f) = actualType == textEnvelopeType ttoken -writeFileWithOwnerPermissions - :: FilePath - -> LBS.ByteString - -> IO (Either (FileError ()) ()) -#ifdef UNIX --- On a unix based system, we grab a file descriptor and set ourselves as owner. --- Since we're holding the file descriptor at this point, we can be sure that --- what we're about to write to is owned by us if an error didn't occur. -writeFileWithOwnerPermissions path a = do - user <- getRealUserID - ownedFile <- try $ - -- We only close the FD on error here, otherwise we let it leak out, since - -- it will be immediately turned into a Handle (which will be closed when - -- the Handle is closed) - bracketOnError - (openFd path WriteOnly (Just ownerModes) defaultFileFlags) - closeFd - (\fd -> setFdOwnerAndGroup fd user (-1) >> pure fd) - case ownedFile of - Left (err :: IOException) -> do - pure $ Left $ FileIOError path err - Right fd -> do - bracket - (fdToHandle fd) - hClose - (\handle -> runExceptT $ handleIOExceptT (FileIOError path) $ LBS.hPut handle a) -#else --- On something other than unix, we make a _new_ file, and since we created it, --- we must own it. We then place it at the target location. Unfortunately this --- won't work correctly with pseudo-files. -writeFileWithOwnerPermissions targetPath a = - bracketOnError - (openTempFile targetDir $ targetFile <.> "tmp") - (\(tmpPath, fHandle) -> do - hClose fHandle >> removeFile tmpPath - return . Left $ FileErrorTempFile targetPath tmpPath fHandle) - (\(tmpPath, fHandle) -> do - LBS.hPut fHandle a - hClose fHandle - renameFile tmpPath targetPath - return $ Right ()) - where - (targetDir, targetFile) = splitFileName targetPath -#endif - writeFileTextEnvelope :: HasTextEnvelope a => FilePath -> Maybe TextEnvelopeDescr @@ -288,9 +223,7 @@ writeFileTextEnvelopeWithOwnerPermissions -> a -> IO (Either (FileError ()) ()) writeFileTextEnvelopeWithOwnerPermissions targetPath mbDescr a = - writeFileWithOwnerPermissions targetPath content - where - content = textEnvelopeToJSON mbDescr a + writeFileWithOwnerPermissions targetPath $ textEnvelopeToJSON mbDescr a textEnvelopeToJSON :: HasTextEnvelope a => Maybe TextEnvelopeDescr -> a -> LBS.ByteString From 7b91bf51b229375d15c922b60619f1aafddbf6d6 Mon Sep 17 00:00:00 2001 From: John Ky Date: Tue, 4 Apr 2023 19:04:39 +1000 Subject: [PATCH 5/8] Rename writeFileWithOwnerPermissions to writeLazyByteStringFileWithOwnerPermissions for consistency --- cardano-api/src/Cardano/Api/IO.hs | 4 ++-- cardano-api/src/Cardano/Api/IO/Compat.hs | 8 ++++---- cardano-api/src/Cardano/Api/SerialiseTextEnvelope.hs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cardano-api/src/Cardano/Api/IO.hs b/cardano-api/src/Cardano/Api/IO.hs index 6bcb768cbb5..a9c06509c5f 100644 --- a/cardano-api/src/Cardano/Api/IO.hs +++ b/cardano-api/src/Cardano/Api/IO.hs @@ -14,11 +14,11 @@ module Cardano.Api.IO , writeTextFile , writeTextOutput - , writeFileWithOwnerPermissions + , writeLazyByteStringFileWithOwnerPermissions ) where import Cardano.Api.Error (FileError (..)) -import Cardano.Api.IO.Compat (writeFileWithOwnerPermissions) +import Cardano.Api.IO.Compat (writeLazyByteStringFileWithOwnerPermissions) import Control.Monad.Except (runExceptT) import Control.Monad.IO.Class (MonadIO (..)) diff --git a/cardano-api/src/Cardano/Api/IO/Compat.hs b/cardano-api/src/Cardano/Api/IO/Compat.hs index c719966165f..2128b737d7d 100644 --- a/cardano-api/src/Cardano/Api/IO/Compat.hs +++ b/cardano-api/src/Cardano/Api/IO/Compat.hs @@ -2,7 +2,7 @@ {-# LANGUAGE ScopedTypeVariables #-} module Cardano.Api.IO.Compat - ( writeFileWithOwnerPermissions + ( writeLazyByteStringFileWithOwnerPermissions ) where #if !defined(mingw32_HOST_OS) @@ -29,7 +29,7 @@ import System.FilePath (splitFileName, (<.>)) import System.IO (hClose, openTempFile) #endif -writeFileWithOwnerPermissions +writeLazyByteStringFileWithOwnerPermissions :: FilePath -> LBS.ByteString -> IO (Either (FileError ()) ()) @@ -37,7 +37,7 @@ writeFileWithOwnerPermissions -- On a unix based system, we grab a file descriptor and set ourselves as owner. -- Since we're holding the file descriptor at this point, we can be sure that -- what we're about to write to is owned by us if an error didn't occur. -writeFileWithOwnerPermissions path a = do +writeLazyByteStringFileWithOwnerPermissions path a = do user <- getRealUserID ownedFile <- try $ -- We only close the FD on error here, otherwise we let it leak out, since @@ -59,7 +59,7 @@ writeFileWithOwnerPermissions path a = do -- On something other than unix, we make a _new_ file, and since we created it, -- we must own it. We then place it at the target location. Unfortunately this -- won't work correctly with pseudo-files. -writeFileWithOwnerPermissions targetPath a = +writeLazyByteStringFileWithOwnerPermissions targetPath a = bracketOnError (openTempFile targetDir $ targetFile <.> "tmp") (\(tmpPath, fHandle) -> do diff --git a/cardano-api/src/Cardano/Api/SerialiseTextEnvelope.hs b/cardano-api/src/Cardano/Api/SerialiseTextEnvelope.hs index 7c52c33a676..831d31ca116 100644 --- a/cardano-api/src/Cardano/Api/SerialiseTextEnvelope.hs +++ b/cardano-api/src/Cardano/Api/SerialiseTextEnvelope.hs @@ -55,7 +55,7 @@ import Cardano.Binary (DecoderError) import Cardano.Api.Error import Cardano.Api.HasTypeProxy -import Cardano.Api.IO (writeFileWithOwnerPermissions, writeLazyByteStringFile) +import Cardano.Api.IO import Cardano.Api.SerialiseCBOR import Cardano.Api.Utils (readFileBlocking) @@ -223,7 +223,7 @@ writeFileTextEnvelopeWithOwnerPermissions -> a -> IO (Either (FileError ()) ()) writeFileTextEnvelopeWithOwnerPermissions targetPath mbDescr a = - writeFileWithOwnerPermissions targetPath $ textEnvelopeToJSON mbDescr a + writeLazyByteStringFileWithOwnerPermissions targetPath $ textEnvelopeToJSON mbDescr a textEnvelopeToJSON :: HasTextEnvelope a => Maybe TextEnvelopeDescr -> a -> LBS.ByteString From 971156d415290b5be2f3c420bf2cdcc5636a4c96 Mon Sep 17 00:00:00 2001 From: John Ky Date: Tue, 4 Apr 2023 19:12:47 +1000 Subject: [PATCH 6/8] Generalise writeLazyByteStringFileWithOwnerPermissions to handleFileForWritingWithOwnerPermission --- cardano-api/cardano-api.cabal | 1 - cardano-api/src/Cardano/Api/IO.hs | 74 ++++++++++++++++++++++- cardano-api/src/Cardano/Api/IO/Compat.hs | 75 ------------------------ 3 files changed, 73 insertions(+), 77 deletions(-) delete mode 100644 cardano-api/src/Cardano/Api/IO/Compat.hs diff --git a/cardano-api/cardano-api.cabal b/cardano-api/cardano-api.cabal index bb95bc92837..c8ff0fb8f38 100644 --- a/cardano-api/cardano-api.cabal +++ b/cardano-api/cardano-api.cabal @@ -70,7 +70,6 @@ library Cardano.Api.HasTypeProxy Cardano.Api.InMode Cardano.Api.IO - Cardano.Api.IO.Compat Cardano.Api.IPC Cardano.Api.IPC.Monad Cardano.Api.IPC.Version diff --git a/cardano-api/src/Cardano/Api/IO.hs b/cardano-api/src/Cardano/Api/IO.hs index a9c06509c5f..87ef2299d1e 100644 --- a/cardano-api/src/Cardano/Api/IO.hs +++ b/cardano-api/src/Cardano/Api/IO.hs @@ -1,6 +1,8 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ScopedTypeVariables #-} module Cardano.Api.IO ( OutputFile(..) @@ -17,8 +19,25 @@ module Cardano.Api.IO , writeLazyByteStringFileWithOwnerPermissions ) where +#if !defined(mingw32_HOST_OS) +#define UNIX +#endif + +#ifdef UNIX +import Control.Exception (IOException, bracket, bracketOnError, try) +import System.Directory () +import System.IO (hClose) +import System.Posix.Files (ownerModes, setFdOwnerAndGroup) +import System.Posix.IO (OpenMode (..), closeFd, defaultFileFlags, fdToHandle, openFd) +import System.Posix.User (getRealUserID) +#else +import Control.Exception (bracketOnError) +import System.Directory (removeFile, renameFile) +import System.FilePath (splitFileName, (<.>)) +import System.IO (hClose, openTempFile) +#endif + import Cardano.Api.Error (FileError (..)) -import Cardano.Api.IO.Compat (writeLazyByteStringFileWithOwnerPermissions) import Control.Monad.Except (runExceptT) import Control.Monad.IO.Class (MonadIO (..)) @@ -33,6 +52,51 @@ import Data.String (IsString) import Data.Text (Text) import qualified Data.Text.IO as Text import GHC.Generics (Generic) +import System.IO (Handle) + +handleFileForWritingWithOwnerPermission + :: FilePath + -> (Handle -> IO ()) + -> IO (Either (FileError ()) ()) +handleFileForWritingWithOwnerPermission path f = do +#ifdef UNIX + -- On a unix based system, we grab a file descriptor and set ourselves as owner. + -- Since we're holding the file descriptor at this point, we can be sure that + -- what we're about to write to is owned by us if an error didn't occur. + user <- getRealUserID + ownedFile <- try $ + -- We only close the FD on error here, otherwise we let it leak out, since + -- it will be immediately turned into a Handle (which will be closed when + -- the Handle is closed) + bracketOnError + (openFd path WriteOnly (Just ownerModes) defaultFileFlags) + closeFd + (\fd -> setFdOwnerAndGroup fd user (-1) >> pure fd) + case ownedFile of + Left (err :: IOException) -> do + pure $ Left $ FileIOError path err + Right fd -> do + bracket + (fdToHandle fd) + hClose + (runExceptT . handleIOExceptT (FileIOError path) . f) +#else + -- On something other than unix, we make a _new_ file, and since we created it, + -- we must own it. We then place it at the target location. Unfortunately this + -- won't work correctly with pseudo-files. + bracketOnError + (openTempFile targetDir $ targetFile <.> "tmp") + (\(tmpPath, h) -> do + hClose h >> removeFile tmpPath + return . Left $ FileErrorTempFile path tmpPath h) + (\(tmpPath, h) -> do + f h + hClose h + renameFile tmpPath path + return $ Right ()) + where + (targetDir, targetFile) = splitFileName path +#endif newtype OutputFile = OutputFile { unOutputFile :: FilePath @@ -69,3 +133,11 @@ writeTextOutput mOutput t = runExceptT $ case mOutput of Just fp -> handleIOExceptT (FileIOError fp) $ Text.writeFile fp t Nothing -> liftIO $ Text.putStr t + +writeLazyByteStringFileWithOwnerPermissions + :: FilePath + -> LBS.ByteString + -> IO (Either (FileError ()) ()) +writeLazyByteStringFileWithOwnerPermissions fp lbs = + handleFileForWritingWithOwnerPermission fp $ \h -> + LBS.hPut h lbs diff --git a/cardano-api/src/Cardano/Api/IO/Compat.hs b/cardano-api/src/Cardano/Api/IO/Compat.hs deleted file mode 100644 index 2128b737d7d..00000000000 --- a/cardano-api/src/Cardano/Api/IO/Compat.hs +++ /dev/null @@ -1,75 +0,0 @@ -{-# LANGUAGE CPP #-} -{-# LANGUAGE ScopedTypeVariables #-} - -module Cardano.Api.IO.Compat - ( writeLazyByteStringFileWithOwnerPermissions - ) where - -#if !defined(mingw32_HOST_OS) -#define UNIX -#endif - -import Cardano.Api.Error (FileError (..)) - -import Control.Monad.Except (runExceptT) -import Control.Monad.Trans.Except.Extra (handleIOExceptT) -import qualified Data.ByteString.Lazy as LBS - -#ifdef UNIX -import Control.Exception (IOException, bracket, bracketOnError, try) -import System.Directory () -import System.IO (hClose) -import System.Posix.Files (ownerModes, setFdOwnerAndGroup) -import System.Posix.IO (OpenMode (..), closeFd, defaultFileFlags, fdToHandle, openFd) -import System.Posix.User (getRealUserID) -#else -import Control.Exception (bracketOnError) -import System.Directory (removeFile, renameFile) -import System.FilePath (splitFileName, (<.>)) -import System.IO (hClose, openTempFile) -#endif - -writeLazyByteStringFileWithOwnerPermissions - :: FilePath - -> LBS.ByteString - -> IO (Either (FileError ()) ()) -#ifdef UNIX --- On a unix based system, we grab a file descriptor and set ourselves as owner. --- Since we're holding the file descriptor at this point, we can be sure that --- what we're about to write to is owned by us if an error didn't occur. -writeLazyByteStringFileWithOwnerPermissions path a = do - user <- getRealUserID - ownedFile <- try $ - -- We only close the FD on error here, otherwise we let it leak out, since - -- it will be immediately turned into a Handle (which will be closed when - -- the Handle is closed) - bracketOnError - (openFd path WriteOnly (Just ownerModes) defaultFileFlags) - closeFd - (\fd -> setFdOwnerAndGroup fd user (-1) >> pure fd) - case ownedFile of - Left (err :: IOException) -> do - pure $ Left $ FileIOError path err - Right fd -> do - bracket - (fdToHandle fd) - hClose - (\handle -> runExceptT $ handleIOExceptT (FileIOError path) $ LBS.hPut handle a) -#else --- On something other than unix, we make a _new_ file, and since we created it, --- we must own it. We then place it at the target location. Unfortunately this --- won't work correctly with pseudo-files. -writeLazyByteStringFileWithOwnerPermissions targetPath a = - bracketOnError - (openTempFile targetDir $ targetFile <.> "tmp") - (\(tmpPath, fHandle) -> do - hClose fHandle >> removeFile tmpPath - return . Left $ FileErrorTempFile targetPath tmpPath fHandle) - (\(tmpPath, fHandle) -> do - LBS.hPut fHandle a - hClose fHandle - renameFile tmpPath targetPath - return $ Right ()) - where - (targetDir, targetFile) = splitFileName targetPath -#endif From 9076213a12ce6353def4a5c29e54dbc76b820863 Mon Sep 17 00:00:00 2001 From: John Ky Date: Tue, 4 Apr 2023 19:28:01 +1000 Subject: [PATCH 7/8] New functions for completeness: * writeByteStringFileWithOwnerPermissions * writeTextFileWithOwnerPermissions --- cardano-api/src/Cardano/Api.hs | 3 +++ cardano-api/src/Cardano/Api/IO.hs | 36 +++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/cardano-api/src/Cardano/Api.hs b/cardano-api/src/Cardano/Api.hs index 69d94783b8e..a8c4ef05d93 100644 --- a/cardano-api/src/Cardano/Api.hs +++ b/cardano-api/src/Cardano/Api.hs @@ -36,12 +36,15 @@ module Cardano.Api ( -- ** IO OutputFile(..), + writeByteStringFileWithOwnerPermissions, writeByteStringFile, writeByteStringOutput, + writeLazyByteStringFileWithOwnerPermissions, writeLazyByteStringFile, writeLazyByteStringOutput, + writeTextFileWithOwnerPermissions, writeTextFile, writeTextOutput, diff --git a/cardano-api/src/Cardano/Api/IO.hs b/cardano-api/src/Cardano/Api/IO.hs index 87ef2299d1e..223002bff96 100644 --- a/cardano-api/src/Cardano/Api/IO.hs +++ b/cardano-api/src/Cardano/Api/IO.hs @@ -7,16 +7,18 @@ module Cardano.Api.IO ( OutputFile(..) + , writeByteStringFileWithOwnerPermissions , writeByteStringFile , writeByteStringOutput + , writeLazyByteStringFileWithOwnerPermissions , writeLazyByteStringFile , writeLazyByteStringOutput + , writeTextFileWithOwnerPermissions , writeTextFile , writeTextOutput - , writeLazyByteStringFileWithOwnerPermissions ) where #if !defined(mingw32_HOST_OS) @@ -108,6 +110,14 @@ writeByteStringFile :: MonadIO m => FilePath -> ByteString -> m (Either (FileErr writeByteStringFile fp bs = runExceptT $ handleIOExceptT (FileIOError fp) $ BS.writeFile fp bs +writeByteStringFileWithOwnerPermissions + :: FilePath + -> BS.ByteString + -> IO (Either (FileError ()) ()) +writeByteStringFileWithOwnerPermissions fp bs = + handleFileForWritingWithOwnerPermission fp $ \h -> + BS.hPut h bs + writeByteStringOutput :: MonadIO m => Maybe FilePath -> ByteString -> m (Either (FileError ()) ()) writeByteStringOutput mOutput bs = runExceptT $ case mOutput of @@ -118,6 +128,14 @@ writeLazyByteStringFile :: MonadIO m => FilePath -> LBS.ByteString -> m (Either writeLazyByteStringFile fp bs = runExceptT $ handleIOExceptT (FileIOError fp) $ LBS.writeFile fp bs +writeLazyByteStringFileWithOwnerPermissions + :: FilePath + -> LBS.ByteString + -> IO (Either (FileError ()) ()) +writeLazyByteStringFileWithOwnerPermissions fp lbs = + handleFileForWritingWithOwnerPermission fp $ \h -> + LBS.hPut h lbs + writeLazyByteStringOutput :: MonadIO m => Maybe FilePath -> LBS.ByteString -> m (Either (FileError ()) ()) writeLazyByteStringOutput mOutput bs = runExceptT $ case mOutput of @@ -128,16 +146,16 @@ writeTextFile :: MonadIO m => FilePath -> Text -> m (Either (FileError ()) ()) writeTextFile fp t = runExceptT $ handleIOExceptT (FileIOError fp) $ Text.writeFile fp t +writeTextFileWithOwnerPermissions + :: FilePath + -> Text + -> IO (Either (FileError ()) ()) +writeTextFileWithOwnerPermissions fp t = + handleFileForWritingWithOwnerPermission fp $ \h -> + Text.hPutStr h t + writeTextOutput :: MonadIO m => Maybe FilePath -> Text -> m (Either (FileError ()) ()) writeTextOutput mOutput t = runExceptT $ case mOutput of Just fp -> handleIOExceptT (FileIOError fp) $ Text.writeFile fp t Nothing -> liftIO $ Text.putStr t - -writeLazyByteStringFileWithOwnerPermissions - :: FilePath - -> LBS.ByteString - -> IO (Either (FileError ()) ()) -writeLazyByteStringFileWithOwnerPermissions fp lbs = - handleFileForWritingWithOwnerPermission fp $ \h -> - LBS.hPut h lbs From 2d11855071fa46a4d50733c59b2d333e439d7032 Mon Sep 17 00:00:00 2001 From: John Ky Date: Thu, 6 Apr 2023 12:40:30 +1000 Subject: [PATCH 8/8] Remove writeFileTextEnvelopeWithOwnerPermissions. Use writeLazyByteStringFileWithOwnerPermissions and textEnvelopeToJSON instead --- cardano-api/src/Cardano/Api.hs | 1 - .../src/Cardano/Api/SerialiseTextEnvelope.hs | 12 ------------ cardano-cli/src/Cardano/CLI/Shelley/Run/Node.hs | 3 ++- .../test/Test/Cardano/Node/FilePermissions.hs | 16 ++++++++-------- 4 files changed, 10 insertions(+), 22 deletions(-) diff --git a/cardano-api/src/Cardano/Api.hs b/cardano-api/src/Cardano/Api.hs index a8c4ef05d93..e214e00d197 100644 --- a/cardano-api/src/Cardano/Api.hs +++ b/cardano-api/src/Cardano/Api.hs @@ -538,7 +538,6 @@ module Cardano.Api ( deserialiseFromTextEnvelope, readFileTextEnvelope, writeFileTextEnvelope, - writeFileTextEnvelopeWithOwnerPermissions, readTextEnvelopeFromFile, readTextEnvelopeOfTypeFromFile, diff --git a/cardano-api/src/Cardano/Api/SerialiseTextEnvelope.hs b/cardano-api/src/Cardano/Api/SerialiseTextEnvelope.hs index 831d31ca116..8d7cda7db8f 100644 --- a/cardano-api/src/Cardano/Api/SerialiseTextEnvelope.hs +++ b/cardano-api/src/Cardano/Api/SerialiseTextEnvelope.hs @@ -18,7 +18,6 @@ module Cardano.Api.SerialiseTextEnvelope , deserialiseFromTextEnvelope , readFileTextEnvelope , writeFileTextEnvelope - , writeFileTextEnvelopeWithOwnerPermissions , readTextEnvelopeFromFile , readTextEnvelopeOfTypeFromFile , textEnvelopeToJSON @@ -215,17 +214,6 @@ writeFileTextEnvelope :: HasTextEnvelope a writeFileTextEnvelope outputFile mbDescr a = writeLazyByteStringFile outputFile (textEnvelopeToJSON mbDescr a) - -writeFileTextEnvelopeWithOwnerPermissions - :: HasTextEnvelope a - => FilePath - -> Maybe TextEnvelopeDescr - -> a - -> IO (Either (FileError ()) ()) -writeFileTextEnvelopeWithOwnerPermissions targetPath mbDescr a = - writeLazyByteStringFileWithOwnerPermissions targetPath $ textEnvelopeToJSON mbDescr a - - textEnvelopeToJSON :: HasTextEnvelope a => Maybe TextEnvelopeDescr -> a -> LBS.ByteString textEnvelopeToJSON mbDescr a = encodePretty' textEnvelopeJSONConfig (serialiseToTextEnvelope mbDescr a) <> "\n" diff --git a/cardano-cli/src/Cardano/CLI/Shelley/Run/Node.hs b/cardano-cli/src/Cardano/CLI/Shelley/Run/Node.hs index b146baf2b80..6f6dd53cc80 100644 --- a/cardano-cli/src/Cardano/CLI/Shelley/Run/Node.hs +++ b/cardano-cli/src/Cardano/CLI/Shelley/Run/Node.hs @@ -129,7 +129,8 @@ runNodeKeyGenVRF (VerificationKeyFile vkeyPath) (SigningKeyFile skeyPath) = do let vkey = getVerificationKey skey firstExceptT ShelleyNodeCmdWriteFileError . newExceptT - $ writeFileTextEnvelopeWithOwnerPermissions skeyPath (Just skeyDesc) skey + $ writeLazyByteStringFileWithOwnerPermissions skeyPath + $ textEnvelopeToJSON (Just skeyDesc) skey firstExceptT ShelleyNodeCmdWriteFileError . newExceptT $ writeLazyByteStringFile vkeyPath diff --git a/cardano-node/test/Test/Cardano/Node/FilePermissions.hs b/cardano-node/test/Test/Cardano/Node/FilePermissions.hs index b0970712047..e3a282604ef 100644 --- a/cardano-node/test/Test/Cardano/Node/FilePermissions.hs +++ b/cardano-node/test/Test/Cardano/Node/FilePermissions.hs @@ -18,21 +18,21 @@ import System.Directory (removeFile) import Cardano.Api import Cardano.Node.Run (checkVRFFilePermissions) import Control.Exception (bracket) -import Control.Monad (Monad(..)) -import Control.Monad.Except(MonadIO(liftIO), runExceptT ) +import Control.Monad (Monad (..)) +import Control.Monad.Except (MonadIO (liftIO), runExceptT) import Data.Bool (Bool, not) -import Data.Either (Either(..)) +import Data.Either (Either (..)) import Data.Eq ((==)) import Data.Foldable (foldl', length) -import Data.Function (($), (.), const) -import Data.Maybe (Maybe(..)) -import Data.Semigroup (Semigroup(..)) +import Data.Function (const, ($), (.)) import qualified Data.List as L +import Data.Maybe (Maybe (..)) +import Data.Semigroup (Semigroup (..)) import Hedgehog (Property, PropertyT, property, success) import qualified Hedgehog import Hedgehog.Internal.Property (Group (..), failWith) import System.IO (FilePath, IO) -import Text.Show (Show(..)) +import Text.Show (Show (..)) #ifdef UNIX import Cardano.Node.Types (VRFPrivateKeyFilePermissionError (..)) @@ -63,7 +63,7 @@ prop_createVRFFileWithOwnerPermissions = createFileWithOwnerPermissions :: HasTextEnvelope a => FilePath -> a -> PropertyT IO () createFileWithOwnerPermissions targetfp value = do - result <- liftIO $ writeFileTextEnvelopeWithOwnerPermissions targetfp Nothing value + result <- liftIO $ writeLazyByteStringFileWithOwnerPermissions targetfp $ textEnvelopeToJSON Nothing value case result of Left err -> failWith Nothing $ displayError err Right () -> return ()