Skip to content

Commit 7215ce8

Browse files
committed
nar: fix executable permissions logic
Nix doesn't use `access` to check whether a file is executable. It instead checks whether the owner executable bit is set. When unpacking a NAR, Nix sets the executable bits for the owner, group, and other.
1 parent 13c101a commit 7215ce8

File tree

1 file changed

+35
-6
lines changed

1 file changed

+35
-6
lines changed

hnix-store-nar/src/System/Nix/Nar/Effects.hs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ module System.Nix.Nar.Effects
44
( NarEffects(..)
55
, narEffectsIO
66
, IsExecutable(..)
7+
, isExecutable
8+
, setExecutable
79
) where
810

911
import Control.Monad.Trans.Control (MonadBaseControl)
@@ -19,10 +21,21 @@ import qualified Data.ByteString
1921
import qualified Data.ByteString.Lazy as Bytes.Lazy
2022
import qualified System.Directory as Directory
2123
import System.Posix.Files ( createSymbolicLink
24+
, fileMode
2225
, fileSize
26+
, FileStatus
2327
, getFileStatus
28+
, getSymbolicLinkStatus
29+
, groupExecuteMode
30+
, intersectFileModes
2431
, isDirectory
32+
, isRegularFile
33+
, nullFileMode
34+
, otherExecuteMode
35+
, ownerExecuteMode
2536
, readSymbolicLink
37+
, setFileMode
38+
, unionFileModes
2639
)
2740
import qualified System.IO as IO
2841
import qualified Control.Exception.Lifted as Exception.Lifted
@@ -59,13 +72,13 @@ narEffectsIO = NarEffects {
5972
narReadFile = liftIO . Bytes.Lazy.readFile
6073
, narWriteFile = \f e c -> liftIO $ do
6174
Bytes.Lazy.writeFile f c
62-
p <- Directory.getPermissions f
63-
Directory.setPermissions f (p { Directory.executable = e == Executable })
75+
Control.Monad.when (e == Executable) $
76+
setExecutable f
6477
, narStreamFile = streamStringOutIO
6578
, narListDir = liftIO . Directory.listDirectory
6679
, narCreateDir = liftIO . Directory.createDirectory
6780
, narCreateLink = \f -> liftIO . createSymbolicLink f
68-
, narIsExec = liftIO . (fmap (bool NonExecutable Executable . Directory.executable)) . Directory.getPermissions
81+
, narIsExec = liftIO . fmap (bool NonExecutable Executable . isExecutable) . getSymbolicLinkStatus
6982
, narIsDir = fmap isDirectory . liftIO . getFileStatus
7083
, narIsSymLink = liftIO . Directory.pathIsSymbolicLink
7184
, narFileSize = fmap (fromIntegral . fileSize) . liftIO . getFileStatus
@@ -102,10 +115,26 @@ streamStringOutIO f executable getChunk =
102115
liftIO $ Data.ByteString.hPut handle c
103116
go handle
104117
updateExecutablePermissions =
105-
Control.Monad.when (executable == Executable) $ do
106-
p <- Directory.getPermissions f
107-
Directory.setPermissions f (p { Directory.executable = True })
118+
Control.Monad.when (executable == Executable) $
119+
setExecutable f
108120
cleanupException (e :: Exception.Lifted.SomeException) = do
109121
liftIO $ Directory.removeFile f
110122
Control.Monad.fail $
111123
"Failed to stream string to " <> f <> ": " <> show e
124+
125+
-- | Check whether the file is executable by the owner.
126+
isExecutable :: FileStatus -> Bool
127+
isExecutable st =
128+
isRegularFile st
129+
&& fileMode st `intersectFileModes` ownerExecuteMode /= nullFileMode
130+
131+
-- | Set the file to be executable by the owner, group, and others.
132+
setExecutable :: FilePath -> IO ()
133+
setExecutable f = do
134+
st <- getSymbolicLinkStatus f
135+
let p =
136+
fileMode st
137+
`unionFileModes` ownerExecuteMode
138+
`unionFileModes` groupExecuteMode
139+
`unionFileModes` otherExecuteMode
140+
setFileMode f p

0 commit comments

Comments
 (0)