1 {-# OPTIONS_GHC -cpp -fffi #-}
2 {-# LANGUAGE CPP, ForeignFunctionInterface #-}
4 module Darcs
.External
(
5 backupByRenaming
, backupByCopying
,
6 copyFileOrUrl
, speculateFileOrUrl
, copyFilesOrUrls
, copyLocal
, cloneFile
,
7 cloneTree
, cloneTreeExcept
, clonePartialsTree
, clonePaths
,
8 fetchFilePS
, gzFetchFilePS
,
9 sendEmail
, generateEmail
, sendEmailDoc
, resendEmail
,
11 execDocPipe
, execPipeIgnoreError
,
13 pipeDoc
, pipeDocSSH
, execSSH
,
15 Cachable
(Cachable
, Uncachable
, MaxAge
),
19 import Data
.List
( intersperse )
20 import Control
.Monad
( when, zipWithM_ )
21 import System
.Exit
( ExitCode(..) )
22 import System
.Environment
( getEnv )
23 import System
.Cmd
( system )
24 import System
.IO ( hPutStr, hPutStrLn, hGetContents, hClose,
25 openBinaryFile
, IOMode( ReadMode
),
27 hIsTerminalDevice
, stdout, stderr, Handle )
28 import System
.IO.Error
( isDoesNotExistError )
29 import System
.IO.Unsafe
( unsafePerformIO
)
30 import System
.Posix
.Files
( getSymbolicLinkStatus
, isRegularFile
, isDirectory
)
31 import System
.Directory
( createDirectory, getDirectoryContents,
32 doesFileExist, doesDirectoryExist,
33 renameFile, renameDirectory, copyFile
)
34 import System
.Process
( runProcess
, runInteractiveProcess
, waitForProcess
)
35 import Control
.Concurrent
( forkIO
, newEmptyMVar
, putMVar
, takeMVar
)
36 import Control
.Exception
( bracket, try, finally
)
37 import Data
.Char ( toUpper )
38 import Foreign
.C
( CString
, withCString
)
39 import Foreign
.Ptr
( nullPtr
)
41 import System
.Console
.Terminfo
( tiGetNum
, setupTermFromEnv
, getCapability
)
43 import Foreign
.C
( CChar
, CInt
)
44 import Foreign
.Ptr
( Ptr
)
45 import Foreign
.Marshal
.Alloc
(allocaBytes
)
46 import Autoconf
( use_color
)
48 import System
.Posix
.Files
( createLink
)
49 import System
.Directory
( createDirectoryIfMissing
)
51 import Darcs
.Flags
( DarcsFlag
( SignAs
, Sign
, SignSSL
, NoLinks
,
53 import Darcs
.RepoPath
( AbsolutePath
, toFilePath
)
54 import Darcs
.Utils
( withCurrentDirectory
, breakCommand
, get_viewer
, ortryrunning
, )
55 import Darcs
.Progress
( withoutProgress
, progressList
, debugMessage
)
57 import ByteStringUtils
(gzReadFilePS
, linesPS
, unlinesPS
)
58 import qualified Data
.ByteString
as B
(ByteString
, empty, null, readFile -- ratify readFile: Just an import from ByteString
59 ,hGetContents, writeFile, hPut
, length -- ratify hGetContents: importing from ByteString
60 ,take, concat, drop, isPrefixOf)
61 import qualified Data
.ByteString
.Char8
as BC
(unpack
, pack
)
63 import Darcs
.Lock
( withTemp
, withOpenTemp
, tempdir_loc
, canonFilename
, writeDocBinFile
,
64 removeFileMayNotExist
, )
65 import CommandLine
( parseCmd
, addUrlencoded
)
66 import Autoconf
( have_libcurl
, have_libwww
, have_HTTP
, have_sendmail
, have_mapi
, sendmail_path
, darcs_version
)
67 import URL
( copyUrl
, copyUrlFirst
, waitUrl
)
68 import Ssh
( getSSH
, copySSH
, copySSHs
, SSHCmd
(..) )
69 import URL
( Cachable
(..) )
70 import Exec
( exec
, Redirect
(..), withoutNonBlock
)
71 import UglyFileName
( fn2fp
, fp2fn
, norm_path
)
72 import Darcs
.URL
( is_file
, is_url
, is_ssh
)
73 import Darcs
.Utils
( catchall
)
74 import Printer
( Doc
, Printers
, putDocLnWith
, hPutDoc
, hPutDocLn
, hPutDocWith
, ($$), (<+>), renderPS
,
76 text
, empty, packedString
, vcat
, renderString
)
77 #include
"impossible.h"
79 backupByRenaming
:: FilePath -> IO ()
80 backupByRenaming
= backupBy rename
82 isD
<- doesDirectoryExist x
83 if isD
then renameDirectory x y
else renameFile x y
85 backupByCopying
:: FilePath -> IO ()
86 backupByCopying
= backupBy copy
89 isD
<- doesDirectoryExist x
90 if isD
then do createDirectory y
91 cloneTree
(do_norm x
) (do_norm y
)
93 do_norm f
= fn2fp
$ norm_path
$ fp2fn f
95 backupBy
:: (FilePath -> FilePath -> IO ()) -> FilePath -> IO ()
97 do hasBF
<- doesFileExist f
98 hasBD
<- doesDirectoryExist f
99 when (hasBF || hasBD
) $ helper
(0::Int)
101 helper i
= do existsF
<- doesFileExist next
102 existsD
<- doesDirectoryExist next
103 if (existsF || existsD
)
105 else do putStrLn $ "Backing up " ++ f
++ "(" ++ suffix
++ ")"
107 where next = f
++ suffix
108 suffix
= "-darcs-backup" ++ show i
110 fetchFilePS
:: String -> Cachable
-> IO B
.ByteString
111 fetchFilePS fou _ | is_file fou
= B
.readFile fou
112 fetchFilePS fou cache
= withTemp
$ \t -> do copyFileOrUrl
[] fou t cache
115 gzFetchFilePS
:: String -> Cachable
-> IO B
.ByteString
116 gzFetchFilePS fou _ | is_file fou
= gzReadFilePS fou
117 gzFetchFilePS fou cache
= withTemp
$ \t-> do copyFileOrUrl
[] fou t cache
121 copyFileOrUrl
:: [DarcsFlag
] -> FilePath -> FilePath -> Cachable
-> IO ()
122 copyFileOrUrl opts fou out _ | is_file fou
= copyLocal opts fou out
123 copyFileOrUrl _ fou out cache | is_url fou
= copyRemote fou out cache
124 copyFileOrUrl _ fou out _ | is_ssh fou
= copySSH fou out
125 copyFileOrUrl _ fou _ _
= fail $ "unknown transport protocol: " ++ fou
127 speculateFileOrUrl
:: String -> FilePath -> IO ()
128 speculateFileOrUrl fou out | is_url fou
= speculateRemote fou out
129 |
otherwise = return ()
131 copyLocal
:: [DarcsFlag
] -> String -> FilePath -> IO ()
132 copyLocal opts fou out | NoLinks `
elem` opts
= cloneFile fou out
133 |
otherwise = createLink fou out `catchall` cloneFile fou out
135 clonePaths
:: FilePath -> FilePath -> [FilePath] -> IO ()
136 clonePaths source dest
= mapM_ (clonePath source dest
)
138 clonePath
:: FilePath -> FilePath -> FilePath -> IO ()
139 clonePath source dest path
140 = do let source
' = source
++ "/" ++ path
141 dest
' = dest
++ "/" ++ path
142 fs
<- getSymbolicLinkStatus source
'
143 if isDirectory fs
then do
144 createDirectoryIfMissing
True dest
'
145 else if isRegularFile fs
then do
146 createDirectoryIfMissing
True (dest
++ "/" ++ basename path
)
147 cloneFile source
' dest
'
148 else fail ("clonePath: Bad file " ++ source
')
149 `
catch`
fail ("clonePath: Bad file " ++ source
++ "/" ++ path
)
150 where basename
= reverse . dropWhile ('/' /=) . dropWhile ('/' ==) . reverse
152 clonePartialsTree
:: FilePath -> FilePath -> [FilePath] -> IO ()
153 clonePartialsTree source dest
= mapM_ (clonePartialTree source dest
)
155 clonePartialTree
:: FilePath -> FilePath -> FilePath -> IO ()
156 clonePartialTree source dest
"" = cloneTree source dest
157 clonePartialTree source dest pref
158 = do createDirectoryIfMissing
True (dest
++ "/" ++ basename pref
)
159 cloneSubTree
(source
++ "/" ++ pref
) (dest
++ "/" ++ pref
)
160 where basename
= reverse . dropWhile ('/' /=) . dropWhile ('/' ==) . reverse
162 cloneTree
:: FilePath -> FilePath -> IO ()
163 cloneTree
= cloneTreeExcept
[]
165 cloneTreeExcept
:: [FilePath] -> FilePath -> FilePath -> IO ()
166 cloneTreeExcept except source dest
=
167 do fs
<- getSymbolicLinkStatus source
168 if isDirectory fs
then do
169 fps
<- getDirectoryContents source
170 let fps
' = filter (`
notElem`
(".":"..":except
)) fps
171 mk_source fp
= source
++ "/" ++ fp
172 mk_dest fp
= dest
++ "/" ++ fp
173 zipWithM_ cloneSubTree
(map mk_source fps
') (map mk_dest fps
')
174 else fail ("cloneTreeExcept: Bad source " ++ source
)
175 `
catch`
fail ("cloneTreeExcept: Bad source " ++ source
)
177 cloneSubTree
:: FilePath -> FilePath -> IO ()
178 cloneSubTree source dest
=
179 do fs
<- getSymbolicLinkStatus source
180 if isDirectory fs
then do
182 fps
<- getDirectoryContents source
183 let fps
' = filter (`
notElem`
[".", ".."]) fps
184 mk_source fp
= source
++ "/" ++ fp
185 mk_dest fp
= dest
++ "/" ++ fp
186 zipWithM_ cloneSubTree
(map mk_source fps
') (map mk_dest fps
')
187 else if isRegularFile fs
then do
188 cloneFile source dest
189 else fail ("cloneSubTree: Bad source "++ source
)
190 `
catch`
(\e
-> if isDoesNotExistError e
194 cloneFile
:: FilePath -> FilePath -> IO ()
197 maybeURLCmd
:: String -> String -> IO(Maybe(String))
198 maybeURLCmd what url
=
199 do let prot
= map toUpper $ takeWhile (/= ':') url
200 fmap Just
(getEnv ("DARCS_" ++ what
++ "_" ++ prot
))
201 `
catch`
\_
-> return Nothing
203 speculateRemote
:: String -> FilePath -> IO () -- speculations are always Cachable
204 speculateRemote u v
=
205 do maybeget
<- maybeURLCmd
"GET" u
207 Just _
-> return () -- can't pipeline these
208 Nothing
-> if have_libwww || have_libcurl || have_HTTP
209 then copyUrl u v Cachable
212 copyRemote
:: String -> FilePath -> Cachable
-> IO ()
213 copyRemote u v cache
=
214 do maybeget
<- maybeURLCmd
"GET" u
216 Nothing
-> copyRemoteNormal u v cache
218 do let (cmd
,args
) = breakCommand get
219 r
<- exec cmd
(args
++[u
]) (Null
, File v
, AsIs
)
220 when (r
/= ExitSuccess
) $
221 fail $ "(" ++ get
++ ") failed to fetch: " ++ u
223 copyRemoteNormal
:: String -> FilePath -> Cachable
-> IO ()
224 copyRemoteNormal u v cache
= if have_libwww || have_libcurl || have_HTTP
225 then copyUrlFirst u v cache
>> waitUrl u
226 else copyRemoteCmd u v
228 copyFilesOrUrls
:: [DarcsFlag
]->FilePath->[String]->FilePath->Cachable
->IO ()
229 copyFilesOrUrls opts dou ns out _ | is_file dou
= copyLocals opts dou ns out
230 copyFilesOrUrls _ dou ns out c | is_url dou
= copyRemotes dou ns out c
231 copyFilesOrUrls _ dou ns out _ | is_ssh dou
= copySSHs dou ns out
232 copyFilesOrUrls _ dou _ _ _
= fail $ "unknown transport protocol: "++dou
235 copyLocals
:: [DarcsFlag
] -> String -> [String] -> FilePath -> IO ()
236 copyLocals opts u ns d
=
237 doWithPatches
(\n -> copyLocal opts
(u
++"/"++n
) (d
++"/"++n
)) ns
239 copyRemotes
:: String -> [String] -> FilePath -> Cachable
-> IO()
240 copyRemotes u ns d cache
=
241 do maybeget
<- maybeURLCmd
"GET" u
242 maybemget
<- maybeURLCmd
"MGET" u
243 case (maybeget
, maybemget
) of
244 (Nothing
, _
) -> copyRemotesNormal u ns d cache
245 (Just _
, Nothing
) -> doWithPatches
(\n -> copyRemote
(u
++"/"++n
) (d
++"/"++n
) cache
) ns
246 (Just _
, Just mget
) -> mgetRemotes mget u ns d
248 stringToInt
:: String -> Int -> Int
249 stringToInt num def
= case reads num
of [(x
,"")] -> x
252 mgetRemotes
:: String -> String -> [String] -> FilePath -> IO()
253 mgetRemotes _ _
[] _
= return ()
254 mgetRemotes mget u ns d
= do
255 mgetmax
<- getEnv "DARCS_MGETMAX" `
catch`
\_
-> return ""
256 let (nsnow
, nslater
) = splitAt (stringToInt mgetmax
200) ns
257 (cmd
, args
) = breakCommand mget
258 urls
= map (\n -> u
++"/"++n
) nsnow
259 withCurrentDirectory d
$ do
260 r
<- exec cmd
(args
++urls
) (Null
,Null
,AsIs
)
261 when (r
/= ExitSuccess
) $
263 ["(" ++ mget
++ ") failed to fetch files.",
264 "source directory: " ++ d
,
265 "source files:"] ++ (upto
5 nsnow
) ++
266 ["still to go:"] ++ (upto
5 nslater
)
267 mgetRemotes mget u nslater d
269 upto
:: Integer -> [String] -> [String]
271 upto
0 l
= [ "(" ++ (show (length l
)) ++ " more)" ]
272 upto n
(h
: t
) = h
: (upto
(n
- 1) t
)
274 copyRemotesNormal
:: String -> [String] -> FilePath -> Cachable
-> IO()
275 copyRemotesNormal u ns d cache
=
276 if have_libwww || have_libcurl || have_HTTP
277 then do mapM_ (\n -> copyUrl
(u
++"/"++n
) (d
++"/"++n
) cache
) ns
278 doWithPatches
(\n -> waitUrl
(u
++"/"++n
)) ns
279 else wgetRemotes u ns d
281 -- Argh, this means darcs get will fail if we don't have libcurl and don't
283 wgetRemotes
:: String -> [String] -> FilePath -> IO ()
284 wgetRemotes u ns d
= do wget_command
<- getEnv "DARCS_WGET" `
catch`
286 let (wget
, wget_args
) = breakCommand wget_command
287 input
= unlines $ map (\n -> u
++"/"++n
) ns
288 withCurrentDirectory d
$ withOpenTemp
$ \(th
,tn
) ->
291 r
<- exec wget
(wget_args
++["-i",tn
])
293 when (r
/= ExitSuccess
) $
295 ["(wget) failed to fetch files.",
296 "source directory: " ++ d
,
297 "source files:"] ++ ns
299 copyRemoteCmd
:: String -> FilePath -> IO ()
300 copyRemoteCmd s tmp
= do
301 let cmd
= get_ext_cmd
302 r
<- stupidexec
(cmd tmp s
) (Null
,Null
,AsIs
)
303 when (r
/= ExitSuccess
) $
304 fail $ "failed to fetch: " ++ s
++" " ++ show r
305 where stupidexec
[] = bug
"stupidexec without a command"
306 stupidexec xs
= exec
(head xs
) (tail xs
)
308 doWithPatches
:: (String -> IO ()) -> [String] -> IO ()
309 doWithPatches f patches
= mapM_ (\p
-> seq p
$ f p
) $ progressList
"Copying patch" patches
311 {-# NOINLINE get_ext_cmd #-}
312 get_ext_cmd
:: String -> String -> [String]
313 -- Only need to find the command once..
314 get_ext_cmd
= unsafePerformIO get_ext_cmd
'
316 -- Would be better to read possible command lines from config-file..
317 get_ext_cmd
' :: IO (String -> String -> [String])
318 get_ext_cmd
' = try_cmd cmds
319 where cmds
= [("wget", (("--version",0),
320 -- use libcurl for proper cache control
321 \t s
-> ["wget", "-q",
322 "--header=Pragma: no-cache",
323 "--header=Cache-Control: no-cache",
325 ("curl", (("--version",2),
326 \t s
-> ["curl", "-s", "-f", "-L",
327 "-H", "Pragma: no-cache",
328 "-H", "Cache-Control: no-cache",
330 try_cmd
[] = fail $ "I need one of: " ++ cs
331 where cs
= concat $ intersperse ", " (map fst cmds
)
332 try_cmd
((c
,(ok_check
,f
)):cs
) = do
333 True <- can_execute ok_check c
335 `
catch`
(\_
-> try_cmd cs
)
337 -- | Run a command on a remote location without passing it any input or
338 -- reading its output. Return its ExitCode
339 execSSH
:: String -> String -> IO ExitCode
340 execSSH remoteAddr command
=
341 do (ssh
, ssh_args
) <- getSSH SSH remoteAddr
342 debugMessage
$ unwords (ssh
:ssh_args
++[remoteAddr
,command
])
343 withoutProgress
$ do hid
<- runProcess ssh
(ssh_args
++[remoteAddr
,command
])
344 Nothing Nothing Nothing Nothing Nothing
347 pipeDoc
:: String -> [String] -> Doc
-> IO ExitCode
348 pipeDoc c args inp
= withoutNonBlock
$ withoutProgress
$
349 do debugMessage
$ unwords (c
:args
)
350 (i
,o
,e
,pid
) <- runInteractiveProcess c args Nothing Nothing
351 mvare
<- newEmptyMVar
352 forkIO
((hGetContents e
>>= -- ratify hGetContents: it's immediately consumed
354 `finally` putMVar mvare
())
355 mvaro
<- newEmptyMVar
356 forkIO
((hGetContents o
>>= -- ratify hGetContents: it's immediately consumed
358 `finally` putMVar mvaro
())
361 rval
<- waitForProcess pid
364 when (rval
== ExitFailure
127) $
365 putStrLn $ "Command not found:\n "++ show (c
:args
)
368 pipeDocSSH
:: String -> [String] -> Doc
-> IO ExitCode
369 pipeDocSSH remoteAddr args input
=
370 do (ssh
, ssh_args
) <- getSSH SSH remoteAddr
371 pipeDoc ssh
(ssh_args
++ (remoteAddr
:args
)) input
373 sendEmail
:: String -> String -> String -> String -> String -> String -> IO ()
374 sendEmail f t s cc scmd body
=
375 sendEmailDoc f t s cc scmd Nothing
(text body
)
379 :: Handle -- ^ handle to write email to
382 -> String -- ^ Subject
386 generateEmail h f t s cc body
= do
388 text
"To:" <+> text t
389 $$ text
"From:" <+> text f
390 $$ text
"Subject:" <+> text s
392 $$ text
"X-Mail-Originator: Darcs Version Control System"
393 $$ text
("X-Darcs-Version: " ++ darcs_version
)
395 where formated_cc
= if cc
== ""
397 else text
"Cc:" <+> text cc
399 -- | Send an email, optionally containing a patch bundle
400 -- (more precisely, its description and the bundle itself)
404 -> String -- ^ subject
406 -> String -- ^ send command
407 -> Maybe (Doc
, Doc
) -- ^ (content,bundle)
410 sendEmailDoc _
"" _
"" _ _ _
= return ()
411 sendEmailDoc f
"" s cc scmd mbundle body
=
412 sendEmailDoc f cc s
"" scmd mbundle body
413 sendEmailDoc f t s cc scmd mbundle body
=
414 if have_sendmail || scmd
/= "" then do
415 withOpenTemp
$ \(h
,fn
) -> do
416 generateEmail h f t s cc body
418 withOpenTemp
$ \(hat
,at
) -> do
419 ftable
' <- case mbundle
of
420 Just
(content
,bundle
) -> do
421 hPutDocLn hat
$ bundle
422 return [ ('b
', renderString content
) , ('a
', at
) ]
424 return [ ('b
', renderString body
) ]
426 let ftable
= [ ('t
',addressOnly t
),('c
',cc
),('f
',f
),('s
',s
) ] ++ ftable
'
427 r
<- execSendmail ftable scmd fn
428 when (r
/= ExitSuccess
) $ fail ("failed to send mail to: " ++ t
429 ++ "\nPerhaps sendmail is not configured.")
430 else if have_mapi
then do
431 r
<- withCString t
$ \tp
->
432 withCString f
$ \fp
->
433 withCString cc
$ \ccp
->
434 withCString s
$ \sp
->
435 withOpenTemp
$ \(h
,fn
) -> do
438 writeDocBinFile
"mailed_patch" body
439 cfn
<- canonFilename fn
440 withCString cfn
$ \pcfn
->
441 c_send_email fp tp ccp sp nullPtr pcfn
442 when (r
/= 0) $ fail ("failed to send mail to: " ++ t
)
443 else fail $ "no mail facility (sendmail or mapi) located at configure time!"
444 where addressOnly a
=
445 case dropWhile (/= '<') a
of
446 ('<':a2
) -> takeWhile (/= '>') a2
449 resendEmail
:: String -> String -> B
.ByteString
-> IO ()
450 resendEmail
"" _ _
= return ()
451 resendEmail t scmd body
=
452 case (have_sendmail || scmd
/= "", have_mapi
) of
454 withOpenTemp
$ \(h
,fn
) -> do
455 hPutStrLn h
$ "To: "++ t
456 hPutStrLn h
$ find_from
(linesPS body
)
457 hPutStrLn h
$ find_subject
(linesPS body
)
458 hPutDocLn h
$ fixit
$ linesPS body
460 let ftable
= [('t
',t
)]
461 r
<- execSendmail ftable scmd fn
462 when (r
/= ExitSuccess
) $ fail ("failed to send mail to: " ++ t
)
463 (_
, True) -> fail "Don't know how to resend email with MAPI"
464 _
-> fail $ "no mail facility (sendmail or mapi) located at configure time (use the sendmail-command option)!"
465 where br
= BC
.pack
"\r"
466 darcsurl
= BC
.pack
"DarcsURL:"
467 content
= BC
.pack
"Content-"
468 from_start
= BC
.pack
"From:"
469 subject_start
= BC
.pack
"Subject:"
471 | B
.null l
= packedString B
.empty $$ vcat
(map packedString ls
)
472 | l
== br
= packedString B
.empty $$ vcat
(map packedString ls
)
473 | B
.take 9 l
== darcsurl || B
.take 8 l
== content
474 = packedString l
$$ fixit ls
475 |
otherwise = fixit ls
477 find_from
(l
:ls
) | B
.take 5 l
== from_start
= BC
.unpack l
478 |
otherwise = find_from ls
479 find_from
[] = "From: unknown"
480 find_subject
(l
:ls
) | B
.take 8 l
== subject_start
= BC
.unpack l
481 |
otherwise = find_subject ls
482 find_subject
[] = "Subject: (no subject)"
484 execSendmail
:: [(Char,String)] -> String -> String -> IO ExitCode
485 execSendmail ftable scmd fn
=
487 exec sendmail_path
["-i", "-t"] (File fn
, Null
, AsIs
)
488 else case parseCmd
(addUrlencoded ftable
) scmd
of
489 Right
(arg0
:opts
, wantstdin
) ->
490 do let stdin = if wantstdin
then File fn
else Null
491 exec arg0 opts
(stdin, Null
, AsIs
)
492 Left e
-> fail $ ("failed to send mail, invalid sendmail-command: "++(show e
))
493 _
-> fail $ ("failed to send mail, invalid sendmail-command")
496 foreign import ccall
"win32/send_email.h send_email" c_send_email
500 :: CString
-> {- sender -}
501 CString
-> {- recipient -}
503 CString
-> {- subject -}
504 CString
-> {- body -}
505 CString
-> {- path -}
508 c_send_email
= impossible
511 execPSPipe
:: String -> [String] -> B
.ByteString
-> IO B
.ByteString
512 execPSPipe c args ps
= fmap renderPS
516 execDocPipe
:: String -> [String] -> Doc
-> IO Doc
517 execDocPipe c args instr
= withoutProgress
$
518 do (i
,o
,e
,pid
) <- runInteractiveProcess c args Nothing Nothing
519 forkIO
$ hPutDoc i instr
>> hClose i
520 mvare
<- newEmptyMVar
521 forkIO
((hGetContents e
>>= -- ratify hGetContents: it's immediately consumed
523 `finally` putMVar mvare
())
524 out
<- B
.hGetContents o
525 rval
<- waitForProcess pid
528 ExitFailure ec
->fail $ "External program '"++c
++
529 "' failed with exit code "++ show ec
530 ExitSuccess
-> return $ packedString out
532 -- The following is needed for diff, which returns non-zero whenever
534 execPipeIgnoreError
:: String -> [String] -> Doc
-> IO Doc
535 execPipeIgnoreError c args instr
= withoutProgress
$
536 do (i
,o
,e
,pid
) <- runInteractiveProcess c args Nothing Nothing
537 forkIO
$ hPutDoc i instr
>> hClose i
538 mvare
<- newEmptyMVar
539 forkIO
((hGetContents e
>>= -- ratify hGetContents: it's immediately consumed
541 `finally` putMVar mvare
())
542 out
<- B
.hGetContents o
545 return $ packedString out
547 signString
:: [DarcsFlag
] -> Doc
-> IO Doc
548 signString
[] d
= return d
549 signString
(Sign
:_
) d
= signPGP
[] d
550 signString
(SignAs keyid
:_
) d
= signPGP
["--local-user", keyid
] d
551 signString
(SignSSL idf
:_
) d
= signSSL idf d
552 signString
(_
:os
) d
= signString os d
554 signPGP
:: [String] -> Doc
-> IO Doc
555 signPGP args t
= execDocPipe
"gpg" ("--clearsign":args
) t
557 signSSL
:: String -> Doc
-> IO Doc
559 withTemp
$ \cert
-> do
560 opensslPS
["req", "-new", "-key", idfile
,
561 "-outform", "PEM", "-days", "365"]
562 (BC
.pack
"\n\n\n\n\n\n\n\n\n\n\n")
563 >>= opensslPS
["x509", "-req", "-extensions",
564 "v3_ca", "-signkey", idfile
,
565 "-outform", "PEM", "-days", "365"]
566 >>= opensslPS
["x509", "-outform", "PEM"]
568 opensslDoc
["smime", "-sign", "-signer", cert
,
569 "-inkey", idfile
, "-noattr", "-text"] t
570 where opensslDoc
= execDocPipe
"openssl"
571 opensslPS
= execPSPipe
"openssl"
574 verifyPS
:: [DarcsFlag
] -> B
.ByteString
-> IO (Maybe B
.ByteString
)
575 verifyPS
[] ps
= return $ Just ps
576 verifyPS
(Verify pks
:_
) ps
= verifyGPG pks ps
577 verifyPS
(VerifySSL auks
:_
) ps
= verifySSL auks ps
578 verifyPS
(_
:os
) ps
= verifyPS os ps
580 verifyGPG
:: AbsolutePath
-> B
.ByteString
-> IO (Maybe B
.ByteString
)
581 verifyGPG goodkeys s
=
582 withOpenTemp
$ \(th
,tn
) -> do
585 rval
<- exec
"gpg" ["--batch","--no-default-keyring",
586 "--keyring",fix_path
$ toFilePath goodkeys
, "--verify"]
587 (File tn
, Null
, Null
)
589 ExitSuccess
-> return $ Just gpg_fixed_s
591 where gpg_fixed_s
= let
592 not_begin_signature x
=
593 x
/= BC
.pack
"-----BEGIN PGP SIGNED MESSAGE-----"
595 x
/= BC
.pack
"-----BEGIN PGP SIGNED MESSAGE-----\r"
596 in unlinesPS
$ map fix_line
$ tail $ dropWhile not_begin_signature
$ linesPS s
597 fix_line x | B
.length x
< 3 = x
598 | BC
.pack
"- -" `B
.isPrefixOf` x
= B
.drop 2 x
601 fix_sep c | c
=='/' = '\\' |
otherwise = c
602 fix_path p
= map fix_sep p
607 verifySSL
:: AbsolutePath
-> B
.ByteString
-> IO (Maybe B
.ByteString
)
608 verifySSL goodkeys s
= do
609 certdata
<- opensslPS
["smime", "-pk7out"] s
610 >>= opensslPS
["pkcs7", "-print_certs"]
611 cruddy_pk
<- opensslPS
["x509", "-pubkey"] certdata
612 let key_used
= B
.concat $ tail $
613 takeWhile (/= BC
.pack
"-----END PUBLIC KEY-----")
615 in do allowed_keys
<- linesPS `
fmap` B
.readFile (toFilePath goodkeys
)
616 if not $ key_used `
elem` allowed_keys
617 then return Nothing
-- Not an allowed key!
618 else withTemp
$ \cert
->
620 withOpenTemp
$ \(th
,tn
) -> do
623 B
.writeFile cert certdata
624 rval
<- exec
"openssl" ["smime", "-verify", "-CAfile",
625 cert
, "-certfile", cert
]
626 (File tn
, File on
, Null
)
628 ExitSuccess
-> Just `
fmap` B
.readFile on
630 where opensslPS
= execPSPipe
"openssl"
632 can_execute
:: (String,Int) -> String -> IO Bool
633 can_execute
(arg
,expected_return_value
) exe
= do
634 withTemp
$ \junk
-> do
635 ec
<- system (unwords [exe
,arg
,">",junk
])
637 ExitSuccess | expected_return_value
== 0 -> return True
638 ExitFailure r | r
== expected_return_value
-> return True
643 - This function returns number of colors supported by current terminal
644 - or -1 if color output not supported or error occured.
645 - Terminal type determined by TERM env. variable.
647 getTermNColors
:: IO Int
651 t
<- setupTermFromEnv
652 return $ case getCapability t
$ tiGetNum
"colors" of
658 foreign import ccall
"tgetnum" c_tgetnum
:: CString
-> IO CInt
659 foreign import ccall
"tgetent" c_tgetent
:: Ptr CChar
-> CString
-> IO CInt
664 getTermNColors
= if not use_color
666 else do term
<- getEnv "TERM"
667 allocaBytes termioBufSize
(getTermNColorsImpl term
)
668 `
catch`
\_
-> return (-1)
670 getTermNColorsImpl
:: String -> Ptr CChar
-> IO Int
671 getTermNColorsImpl term buf
= do rc
<- withCString term
$
672 \termp
-> c_tgetent buf termp
673 x
<- if (rc
/= 1) then return (-1) else withCString
"Co" $ \capap
-> c_tgetnum capap
674 return $ fromIntegral x
678 getTermNColors
= return (-1)
682 viewDoc
:: Doc
-> IO ()
683 viewDoc
= viewDocWith simplePrinters
685 viewDocWith
:: Printers
-> Doc
-> IO ()
686 viewDocWith pr msg
= do
687 isTerminal
<- hIsTerminalDevice
stdout
688 if isTerminal
&& lengthGreaterThan
(20 :: Int) (lines $ renderString msg
)
689 then do viewer
<- get_viewer
690 pipeDocToPager viewer
[] pr msg
691 `ortryrunning` pipeDocToPager
"less" [] pr msg
692 `ortryrunning` pipeDocToPager
"more" [] pr msg
694 `ortryrunning` pipeDocToPager
"more.com" [] pr msg
696 `ortryrunning` pipeDocToPager
"" [] pr msg
697 else pipeDocToPager
"" [] pr msg
699 where lengthGreaterThan n _ | n
<= 0 = True
700 lengthGreaterThan _
[] = False
701 lengthGreaterThan n
(_
:xs
) = lengthGreaterThan
(n
-1) xs
703 pipeDocToPager
:: String -> [String] -> Printers
-> Doc
-> IO ExitCode
705 pipeDocToPager
"" _ pr inp
= do
709 pipeDocToPager c args pr inp
= withoutNonBlock
$ withoutProgress
$ do
711 bracket (openBinaryTempFile tmp
"darcs-pager") cleanup
$ \(fn
,fh
) ->
712 do hPutDocWith pr fh inp
714 bracket (openBinaryFile fn ReadMode
) hClose $ \h
->
715 do x
<- do pid
<- runProcess c args Nothing Nothing
(Just h
) Nothing Nothing
717 when (x
== ExitFailure
127) $
718 putStrLn $ "Command not found:\n "++ show (c
:args
)
721 cleanup
(f
,h
) = do try $ hClose h
722 removeFileMayNotExist f