如何覆盖Codec.Archive.Tar中的函数

时间:2014-04-15 18:47:17

标签: haskell functional-programming tar

哈斯克尔诺布在这里。我有一个特别关于如何使用现有库的问题,这可能会导致正确使用Haskell的一些更基本的方面。

我正在学习Haskell并在学习的过程中考虑一个小项目。该脚本需要查找给定目录中的所有tarball并将其并行解压缩。在这一点上,我正在研究解包的基本功能。那么,使用Codec.Archive.Tar包,如何使用完全限定的路径覆盖有关tarball的行为?

以下是一些示例代码:

module Main where

import qualified Codec.Archive.Tar as Tar
import qualified Codec.Compression.GZip as GZip
import Control.Monad (liftM, unless)
import qualified Data.ByteString.Lazy as BS
import System.Directory (doesDirectoryExist, getDirectoryContents)
import System.Exit (exitWith, ExitCode(..))
import System.FilePath.Posix (takeExtension)

searchPath = "/home/someuser/tarball/dir"

exit = exitWith ExitSuccess
die = exitWith (ExitFailure 1)

processFile :: String -> IO ()
processFile file = do
    putStrLn $ "Unpacking " ++ file ++ " to " ++ searchPath
    Tar.unpack searchPath . Tar.read . GZip.decompress =<< BS.readFile filePath
    where filePath = searchPath ++ "/" ++ file

main = do
    dirExists <- doesDirectoryExist searchPath
    unless dirExists $ (putStrLn $ "Error: Search path not found: " ++ searchPath) >> die
    files <- targetFiles `liftM` getDirectoryContents searchPath
    mapM_ processFile files
    exit
    where targetFiles = filter (\f -> f /= "." && f /= ".." && takeExtension f == ".tgz")

当我在包含以下内容的tarball的目录中运行它时:

tar czvPf myfile.tgz /tarball_testing/myfile

我得到以下输出:

Unpacking myfile.tgz to /tarball_testing
unpacker.hs: Absolute file name in tar archive: "/tarball_testing/myfile"

第二行是问题。阅读Codec.Archive.Tar的文档我没有看到禁用此功能的方法(对于我为什么要在tarball中使用完整路径的讨论,或者这样做的相对安全含义不感兴趣)。

我想到的第一件事就是我不知何故需要覆盖这个功能,但这并没有感觉到#34;就像职业Haskeller那样做的方式。我能指向正确的方向吗?

1 个答案:

答案 0 :(得分:3)

你不能monkey patch或以其他方式覆盖Haskell模块中的函数,因此没有解决方法可以让你避免库的安全措施。但是,您可以使用Codec.Archive.Tar中的功能在解压缩之前修改tar条目路径,以便它们不再是绝对路径。具体来说,有mapEntriesNoFail函数,类型为

mapEntriesNoFail :: (Entry -> Entry) -> Entries e -> Entries e

EntriesTar.unpack的参数类型,而Entry是单个条目的类型。感谢mapEntriesNoFail,我们的问题就是编写Entry -> Entry函数来调整路径。为此,首先我们需要一些额外的进口:

import qualified Codec.Archive.Tar.Entry as Tar
import System.FilePath.Posix (takeExtension, dropDrive, hasTrailingPathSeparator)
import Data.Either (either)

该功能可以如下所示:

dropDriveFromEntry :: Tar.Entry -> Tar.Entry
dropDriveFromEntry entry =
    either (error "Resulting tar path is somehow too long")
        (\tp -> entry { Tar.entryTarPath = tp })
        drivelessTarPath
    where
    tarPath = Tar.entryTarPath entry
    path = Tar.fromTarPath tarPath
    toTarPath' p = Tar.toTarPath (hasTrailingPathSeparator p) p
    drivelessTarPath = toTarPath' $ dropDrive path

这似乎有点啰嗦;然而,我们跳过的箍是为了确保最终的沥青路径是合理的。您可以在Codec.Archive.Tar.Entry文档中阅读有关tar处理的血腥细节。此定义中的关键函数是dropDrive,它使绝对路径相对(在Linux中,它删除绝对路径的前导斜杠)。

值得在either的使用上花几句话。 toTarPath生成类型为Either String TarPath的值,以说明失败的可能性。具体而言,如果提供的路径太长,则转换为tar路径会失败。但是,在我们的例子中,路径不能太长,因为它是一个已经存在于tar文件中的路径,可能带有删除的前导斜杠。既然如此,使用Either消除either包装就足够了,传递错误而不是函数来处理(不可能的)Left情况。

手持dropDriveFromEntry时,我们只需在解压前将其映射到条目上。您的计划的相关部分将成为:

    Tar.unpack searchPath . Tar.mapEntriesNoFail dropDriveFromEntry
        . Tar.read . GZip.decompress =<< BS.readFile filePath

请注意,如果dropDriveFromEntry中存在相关错误,我们会将其返回Either String TarPath,然后使用mapEntries代替mapEntriesNoFail

通过这些更改,tar文件中的条目将被提取到/home/someuser/tarball/dir/tarball_testing/myfile。如果这不是您的意图,您可以修改dropDriveFromEntry,以便它执行您需要的任何额外路径处理。

P.S。:关于你问题的替代标题,并考虑到你向我们展示的合理的小程序,我认为你不应该担心:)