Initial commit
This commit is contained in:
commit
ddcf0a04db
13
LICENSE
Normal file
13
LICENSE
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Copyright 2022 Arista Networks
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
149
cbits/nix.cpp
Normal file
149
cbits/nix.cpp
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
#include <cstddef>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <nix/store-api.hh>
|
||||||
|
#include "nix.hh"
|
||||||
|
|
||||||
|
using namespace nix;
|
||||||
|
|
||||||
|
// Copied from:
|
||||||
|
//
|
||||||
|
// https://github.com/NixOS/nix/blob/2.8.1/perl/lib/Nix/Store.xs#L24-L37
|
||||||
|
static ref<Store> getStore()
|
||||||
|
{
|
||||||
|
static std::shared_ptr<Store> _store;
|
||||||
|
|
||||||
|
if (!_store) {
|
||||||
|
loadConfFile();
|
||||||
|
|
||||||
|
_store = openStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ref<Store>(_store);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
void freeString(struct string * const input)
|
||||||
|
{
|
||||||
|
free((void *) input->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Perhaps use convention where destination is first argument
|
||||||
|
void copyString(std::string const input, struct string * const output)
|
||||||
|
{
|
||||||
|
size_t const size = input.size();
|
||||||
|
|
||||||
|
char * data = (char *) calloc(size, sizeof(char));
|
||||||
|
|
||||||
|
input.copy(data, size);
|
||||||
|
|
||||||
|
output->size = size;
|
||||||
|
|
||||||
|
output->data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct string emptyString = { .data = NULL, .size = 0 };
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
void freeStrings(struct strings * const input)
|
||||||
|
{
|
||||||
|
size_t size = input->size;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < size; i++) {
|
||||||
|
freeString(&input->data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void copyStrings
|
||||||
|
( std::vector<std::string> input
|
||||||
|
, struct strings * const output
|
||||||
|
)
|
||||||
|
{
|
||||||
|
size_t const size = input.size();
|
||||||
|
|
||||||
|
struct string * data = (struct string *) calloc(input.size(), sizeof(struct string));
|
||||||
|
|
||||||
|
for (size_t i = 0; i < size; i++) {
|
||||||
|
copyString(input[i], &data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
output->data = data;
|
||||||
|
|
||||||
|
output->size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
void getStoreDir(struct string * const output)
|
||||||
|
{
|
||||||
|
copyString(settings.nixStore, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
void queryPathFromHashPart
|
||||||
|
( char const * const hashPart
|
||||||
|
, struct string * const output
|
||||||
|
)
|
||||||
|
{
|
||||||
|
ref<Store> store = getStore();
|
||||||
|
|
||||||
|
std::optional<StorePath> path = store->queryPathFromHashPart(hashPart);
|
||||||
|
|
||||||
|
if (path.has_value()) {
|
||||||
|
copyString(store->printStorePath(path.value()), output);
|
||||||
|
} else {
|
||||||
|
*output = emptyString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void queryPathInfo
|
||||||
|
( char const * const storePath
|
||||||
|
, PathInfo * const output
|
||||||
|
)
|
||||||
|
{
|
||||||
|
ref<Store> store = getStore();
|
||||||
|
|
||||||
|
ref<ValidPathInfo const> const validPathInfo =
|
||||||
|
store->queryPathInfo(store->parseStorePath(storePath));
|
||||||
|
|
||||||
|
std::optional<StorePath const> const deriver = validPathInfo->deriver;
|
||||||
|
|
||||||
|
if (deriver.has_value()) {
|
||||||
|
copyString(store->printStorePath(deriver.value()), &output->deriver);
|
||||||
|
} else {
|
||||||
|
output->deriver = emptyString;
|
||||||
|
};
|
||||||
|
|
||||||
|
copyString(validPathInfo->narHash.to_string(Base32, true), &output->narHash);
|
||||||
|
|
||||||
|
output->narSize = validPathInfo->narSize;
|
||||||
|
|
||||||
|
std::vector<std::string> references(validPathInfo->references.size());
|
||||||
|
|
||||||
|
std::transform(
|
||||||
|
validPathInfo->references.begin(),
|
||||||
|
validPathInfo->references.end(),
|
||||||
|
references.begin(),
|
||||||
|
[=](StorePath storePath) { return store->printStorePath(storePath); }
|
||||||
|
);
|
||||||
|
|
||||||
|
copyStrings(references, &output->references);
|
||||||
|
|
||||||
|
std::vector<std::string> sigs(validPathInfo->sigs.begin(), validPathInfo->sigs.end());
|
||||||
|
|
||||||
|
copyStrings(sigs, &output->sigs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void freePathInfo(struct PathInfo * const input)
|
||||||
|
{
|
||||||
|
freeString(&input->deriver);
|
||||||
|
freeString(&input->narHash);
|
||||||
|
freeStrings(&input->references);
|
||||||
|
freeStrings(&input->sigs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
22
cbits/nix.hh
Normal file
22
cbits/nix.hh
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
struct string {
|
||||||
|
char const * data;
|
||||||
|
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct strings {
|
||||||
|
struct string * data;
|
||||||
|
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PathInfo {
|
||||||
|
struct string deriver;
|
||||||
|
struct string narHash;
|
||||||
|
uint64_t narSize;
|
||||||
|
struct strings references;
|
||||||
|
struct strings sigs;
|
||||||
|
};
|
||||||
4
darwin/Sysctl.hs
Normal file
4
darwin/Sysctl.hs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
module Sysctl where
|
||||||
|
|
||||||
|
_SO_MAX_CONN :: String
|
||||||
|
_SO_MAX_CONN = "kern.ipc.somaxconn"
|
||||||
4
linux/Sysctl.hs
Normal file
4
linux/Sysctl.hs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
module Sysctl where
|
||||||
|
|
||||||
|
_SO_MAX_CONN :: String
|
||||||
|
_SO_MAX_CONN = "net.core.somaxconn"
|
||||||
68
nix-serve-ng.cabal
Normal file
68
nix-serve-ng.cabal
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
cabal-version: 3.0
|
||||||
|
name: nix-serve-ng
|
||||||
|
version: 1.0.0
|
||||||
|
synopsis:
|
||||||
|
A drop-in replacement for nix-serve that's faster and more stable
|
||||||
|
|
||||||
|
license: Apache-2.0
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Arista Networks
|
||||||
|
maintainer: opensource@awakesecurity.com
|
||||||
|
copyright: 2022 Arista Networks
|
||||||
|
|
||||||
|
executable nix-serve-ng
|
||||||
|
hs-source-dirs: src
|
||||||
|
|
||||||
|
main-is: Main.hs
|
||||||
|
|
||||||
|
other-modules: Sysctl
|
||||||
|
, Nix
|
||||||
|
, Options
|
||||||
|
|
||||||
|
if os(darwin)
|
||||||
|
hs-source-dirs: darwin
|
||||||
|
elif os(linux)
|
||||||
|
hs-source-dirs: linux
|
||||||
|
else
|
||||||
|
buildable: False
|
||||||
|
|
||||||
|
-- https://nixos.org/manual/nix/stable/installation/supported-platforms.html
|
||||||
|
if arch(i686) && os(linux)
|
||||||
|
cxx-options: -DSYSTEM="i686-linux"
|
||||||
|
elif arch(x86_64) && os(linux)
|
||||||
|
cxx-options: -DSYSTEM="x86_64-linux"
|
||||||
|
elif arch(aarch64) && os(linux)
|
||||||
|
cxx-options: -DSYSTEM="aarch64-linux"
|
||||||
|
elif arch(aarch64) && os(darwin)
|
||||||
|
cxx-options: -DSYSTEM="aarch64-darwin"
|
||||||
|
elif arch(x86_64) && os(darwin)
|
||||||
|
cxx-options: -DSYSTEM="x86_64-darwin"
|
||||||
|
else
|
||||||
|
buildable: False
|
||||||
|
|
||||||
|
include-dirs: cbits
|
||||||
|
|
||||||
|
cxx-sources: cbits/nix.cpp
|
||||||
|
|
||||||
|
cxx-options: -std=c++17
|
||||||
|
|
||||||
|
build-depends: base < 5
|
||||||
|
, bsd-sysctl
|
||||||
|
, bytestring
|
||||||
|
, http-types
|
||||||
|
, managed
|
||||||
|
, megaparsec
|
||||||
|
, network
|
||||||
|
, optparse-applicative
|
||||||
|
, vector
|
||||||
|
, wai
|
||||||
|
, warp
|
||||||
|
, warp-tls
|
||||||
|
|
||||||
|
extra-libraries: nixstore
|
||||||
|
, nixutil
|
||||||
|
, c++
|
||||||
|
|
||||||
|
default-language: Haskell2010
|
||||||
|
|
||||||
|
ghc-options: -Wall
|
||||||
18
nix/nix-serve-ng.nix
Normal file
18
nix/nix-serve-ng.nix
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{ mkDerivation, base, bsd-sysctl, bytestring, http-types, lib
|
||||||
|
, managed, megaparsec, network, nixstore, nixutil
|
||||||
|
, optparse-applicative, vector, wai, warp, warp-tls
|
||||||
|
}:
|
||||||
|
mkDerivation {
|
||||||
|
pname = "nix-serve-ng";
|
||||||
|
version = "1.0.0";
|
||||||
|
src = ./.;
|
||||||
|
isLibrary = false;
|
||||||
|
isExecutable = true;
|
||||||
|
executableHaskellDepends = [
|
||||||
|
base bsd-sysctl bytestring http-types managed megaparsec network
|
||||||
|
optparse-applicative vector wai warp warp-tls
|
||||||
|
];
|
||||||
|
executableSystemDepends = [ nixstore nixutil ];
|
||||||
|
description = "A drop-in replacement for nix-serve that's faster and more stable";
|
||||||
|
license = lib.licenses.bsd3;
|
||||||
|
}
|
||||||
26
shell.nix
Normal file
26
shell.nix
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
let
|
||||||
|
overlay = pkgsNew: pkgsOld: {
|
||||||
|
haskellPackages = pkgsOld.haskellPackages.override (old: {
|
||||||
|
overrides =
|
||||||
|
pkgsNew.lib.composeExtensions
|
||||||
|
(old.overrides or (_: _: { }))
|
||||||
|
(haskellPackagesNew: haskellPackagesOld: {
|
||||||
|
nix-serve-ng =
|
||||||
|
pkgsNew.haskell.lib.overrideCabal
|
||||||
|
(haskellPackagesNew.callPackage ./nix/nix-serve-ng.nix {
|
||||||
|
nixstore = pkgsNew.nix;
|
||||||
|
nixutil = pkgsNew.nix;
|
||||||
|
})
|
||||||
|
(old: {
|
||||||
|
executableSystemDepends = (old.executableSystemDepends or []) ++ [
|
||||||
|
pkgsNew.boost.dev
|
||||||
|
];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
pkgs = import <nixpkgs> { config = { }; overlays = [ overlay ]; };
|
||||||
|
|
||||||
|
in
|
||||||
|
pkgs.haskellPackages.nix-serve-ng.env
|
||||||
209
src/Main.hs
Normal file
209
src/Main.hs
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
{-# LANGUAGE BlockArguments #-}
|
||||||
|
{-# LANGUAGE MultiWayIf #-}
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
|
||||||
|
module Main where
|
||||||
|
|
||||||
|
import Data.Function ((&))
|
||||||
|
import Network.Socket (SockAddr(..))
|
||||||
|
import Nix (PathInfo(..))
|
||||||
|
import Options (Options(..), Socket(..), SSL(..))
|
||||||
|
import Sysctl (_SO_MAX_CONN)
|
||||||
|
|
||||||
|
import qualified Control.Monad as Monad
|
||||||
|
import qualified Data.ByteString as ByteString
|
||||||
|
import qualified Data.ByteString.Lazy as ByteString.Lazy
|
||||||
|
import qualified Data.ByteString.Builder as Builder
|
||||||
|
import qualified Data.Vector as Vector
|
||||||
|
import qualified Network.HTTP.Types as Types
|
||||||
|
import qualified Network.Socket as Socket
|
||||||
|
import qualified Network.Wai as Wai
|
||||||
|
import qualified Network.Wai.Handler.Warp as Warp
|
||||||
|
import qualified Network.Wai.Handler.WarpTLS as WarpTLS
|
||||||
|
import qualified Nix
|
||||||
|
import qualified Options
|
||||||
|
import qualified Options.Applicative as Options
|
||||||
|
import qualified System.BSD.Sysctl as Sysctl
|
||||||
|
|
||||||
|
toSocket :: FilePath -> IO Socket.Socket
|
||||||
|
toSocket path = do
|
||||||
|
let family = Socket.AF_UNIX
|
||||||
|
|
||||||
|
Monad.unless (Socket.isSupportedFamily family) do
|
||||||
|
fail "Unix domain sockets are not supported on this system"
|
||||||
|
|
||||||
|
socket <- Socket.socket family Socket.Stream Socket.defaultProtocol
|
||||||
|
|
||||||
|
Socket.bind socket (SockAddrUnix path)
|
||||||
|
|
||||||
|
backlog <- Sysctl.sysctlReadInt _SO_MAX_CONN
|
||||||
|
|
||||||
|
Socket.listen socket (fromIntegral backlog)
|
||||||
|
|
||||||
|
return socket
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
options@Options{ priority } <- Options.execParser Options.parserInfo
|
||||||
|
|
||||||
|
storeDirectory <- Nix.getStoreDir
|
||||||
|
|
||||||
|
let stripStore = ByteString.stripPrefix (storeDirectory <> "/")
|
||||||
|
|
||||||
|
let application request respond = do
|
||||||
|
let rawPath = Wai.rawPathInfo request
|
||||||
|
|
||||||
|
if | Just suffix <- ByteString.stripSuffix ".narinfo" rawPath
|
||||||
|
, Just hashPart <- ByteString.stripPrefix "/" suffix -> do
|
||||||
|
maybeStorePath <- Nix.queryPathFromHashPart hashPart
|
||||||
|
|
||||||
|
case maybeStorePath of
|
||||||
|
Nothing -> do
|
||||||
|
let headers = [ ("Content-Type", "text/plain") ]
|
||||||
|
|
||||||
|
let builder = "No such path.\n"
|
||||||
|
|
||||||
|
let response =
|
||||||
|
Wai.responseBuilder
|
||||||
|
Types.status404
|
||||||
|
headers
|
||||||
|
builder
|
||||||
|
|
||||||
|
respond response
|
||||||
|
Just storePath -> do
|
||||||
|
PathInfo{..} <- Nix.queryPathInfo storePath
|
||||||
|
|
||||||
|
case ByteString.stripPrefix "sha256:" narHash of
|
||||||
|
Just narHash2 -> do
|
||||||
|
let referencesBuilder
|
||||||
|
| not (Vector.null references) =
|
||||||
|
case traverse stripStore references of
|
||||||
|
Just names ->
|
||||||
|
"References:"
|
||||||
|
<> foldMap (\name -> " " <> Builder.byteString name) names
|
||||||
|
<> "\n"
|
||||||
|
| otherwise =
|
||||||
|
mempty
|
||||||
|
|
||||||
|
let deriverBuilder =
|
||||||
|
case deriver of
|
||||||
|
Just d ->
|
||||||
|
case stripStore d of
|
||||||
|
Just name ->
|
||||||
|
"Deriver: "
|
||||||
|
<> Builder.byteString name
|
||||||
|
<> "\n"
|
||||||
|
Nothing ->
|
||||||
|
mempty
|
||||||
|
|
||||||
|
let builder =
|
||||||
|
"StorePath: "
|
||||||
|
<> Builder.byteString storePath
|
||||||
|
<> "\nURL: nar/"
|
||||||
|
<> Builder.byteString hashPart
|
||||||
|
<> "-"
|
||||||
|
<> Builder.byteString narHash2
|
||||||
|
<> "\nCompression: none\nNarHash: "
|
||||||
|
<> Builder.byteString narHash
|
||||||
|
<> "\nNarSize: "
|
||||||
|
<> Builder.word64Dec narSize
|
||||||
|
<> "\n"
|
||||||
|
<> referencesBuilder
|
||||||
|
<> deriverBuilder
|
||||||
|
|
||||||
|
let size =
|
||||||
|
( ByteString.Lazy.toStrict
|
||||||
|
. Builder.toLazyByteString
|
||||||
|
. Builder.int64Dec
|
||||||
|
. ByteString.Lazy.length
|
||||||
|
. Builder.toLazyByteString
|
||||||
|
) builder
|
||||||
|
|
||||||
|
let headers =
|
||||||
|
[ ("Content-Type", "text/x-nix-narinfo")
|
||||||
|
, ("Content-Length", size)
|
||||||
|
]
|
||||||
|
|
||||||
|
let response =
|
||||||
|
Wai.responseBuilder
|
||||||
|
Types.status200
|
||||||
|
headers
|
||||||
|
builder
|
||||||
|
|
||||||
|
respond response
|
||||||
|
|
||||||
|
Nothing -> do
|
||||||
|
let headers =
|
||||||
|
[ ("Content-Type", "text/plain") ]
|
||||||
|
|
||||||
|
let builder = "Internal Server Error"
|
||||||
|
|
||||||
|
let response =
|
||||||
|
Wai.responseBuilder
|
||||||
|
Types.status500
|
||||||
|
headers
|
||||||
|
builder
|
||||||
|
|
||||||
|
respond response
|
||||||
|
|
||||||
|
| rawPath == "/nix-cache-info" -> do
|
||||||
|
let headers = [ ("Content-Type", "text/plain") ]
|
||||||
|
|
||||||
|
let builder =
|
||||||
|
"StoreDir: "
|
||||||
|
<> Builder.byteString storeDirectory
|
||||||
|
<> "\nWantMassQuery: 1\nPriority: "
|
||||||
|
<> Builder.integerDec priority
|
||||||
|
<> "\n"
|
||||||
|
|
||||||
|
let response =
|
||||||
|
Wai.responseBuilder Types.status200 headers builder
|
||||||
|
|
||||||
|
respond response
|
||||||
|
|
||||||
|
| otherwise -> do
|
||||||
|
let headers = [ ("Content-Type", "text/plain") ]
|
||||||
|
|
||||||
|
let builder = "File not found.\n"
|
||||||
|
|
||||||
|
let response =
|
||||||
|
Wai.responseBuilder Types.status404 headers builder
|
||||||
|
|
||||||
|
respond response
|
||||||
|
|
||||||
|
case options of
|
||||||
|
Options{ ssl = Disabled, socket = TCP{ host, port } } -> do
|
||||||
|
let settings =
|
||||||
|
Warp.defaultSettings
|
||||||
|
& Warp.setHost host
|
||||||
|
& Warp.setPort port
|
||||||
|
|
||||||
|
Warp.runSettings settings application
|
||||||
|
|
||||||
|
Options{ ssl = Disabled, socket = Unix{ path } } -> do
|
||||||
|
let settings = Warp.defaultSettings
|
||||||
|
|
||||||
|
socket <- toSocket path
|
||||||
|
|
||||||
|
Warp.runSettingsSocket settings socket application
|
||||||
|
|
||||||
|
Options{ ssl = Enabled{ cert, key }, socket = TCP{ host, port } } -> do
|
||||||
|
let tlsSettings = WarpTLS.tlsSettings cert key
|
||||||
|
|
||||||
|
let settings =
|
||||||
|
Warp.defaultSettings
|
||||||
|
& Warp.setHost host
|
||||||
|
& Warp.setPort port
|
||||||
|
|
||||||
|
WarpTLS.runTLS tlsSettings settings application
|
||||||
|
|
||||||
|
Options{ ssl = Enabled{ cert, key }, socket = Unix{ path } } -> do
|
||||||
|
let tlsSettings = WarpTLS.tlsSettings cert key
|
||||||
|
|
||||||
|
let settings = Warp.defaultSettings
|
||||||
|
|
||||||
|
socket <- toSocket path
|
||||||
|
|
||||||
|
WarpTLS.runTLSSocket tlsSettings settings socket application
|
||||||
211
src/Nix.hsc
Normal file
211
src/Nix.hsc
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
{-# LANGUAGE BlockArguments #-}
|
||||||
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
|
{-# LANGUAGE ForeignFunctionInterface #-}
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
|
||||||
|
module Nix where
|
||||||
|
|
||||||
|
import Control.Monad.Managed (Managed)
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import Data.Vector (Vector)
|
||||||
|
import Data.Word (Word64)
|
||||||
|
import Foreign (Ptr, Storable(..))
|
||||||
|
import Foreign.C (CChar, CLong, CSize, CString)
|
||||||
|
|
||||||
|
import qualified Control.Exception as Exception
|
||||||
|
import qualified Control.Monad.Managed as Managed
|
||||||
|
import qualified Data.ByteString as ByteString
|
||||||
|
import qualified Data.Vector as Vector
|
||||||
|
import qualified Data.Vector.Storable as Vector.Storable
|
||||||
|
import qualified Foreign
|
||||||
|
|
||||||
|
#include "nix.hh"
|
||||||
|
|
||||||
|
foreign import ccall "freeString" freeString :: Ptr String_ -> IO ()
|
||||||
|
|
||||||
|
data String_ = String_ { data_ :: Ptr CChar, size :: CSize }
|
||||||
|
|
||||||
|
instance Storable String_ where
|
||||||
|
sizeOf _ = #{size struct string}
|
||||||
|
|
||||||
|
alignment _ = #{alignment struct string}
|
||||||
|
|
||||||
|
peek pointer = do
|
||||||
|
data_ <- #{peek struct string, data} pointer
|
||||||
|
|
||||||
|
size <- #{peek struct string, size} pointer
|
||||||
|
|
||||||
|
return String_{ data_, size }
|
||||||
|
|
||||||
|
poke pointer String_{ data_, size } = do
|
||||||
|
#{poke struct string, data} pointer data_
|
||||||
|
|
||||||
|
#{poke struct string, size} pointer size
|
||||||
|
|
||||||
|
fromString_ :: String_ -> IO ByteString
|
||||||
|
fromString_ String_{ data_, size } =
|
||||||
|
ByteString.packCStringLen (data_, fromIntegral @CSize @Int size)
|
||||||
|
|
||||||
|
toString_ :: ByteString -> Managed String_
|
||||||
|
toString_ bytes = do
|
||||||
|
(data_, size) <- Managed.managed (ByteString.useAsCStringLen bytes)
|
||||||
|
|
||||||
|
return String_{ data_, size = fromIntegral @Int @CSize size }
|
||||||
|
|
||||||
|
foreign import ccall "freeStrings" freeStrings :: Ptr Strings -> IO ()
|
||||||
|
|
||||||
|
data Strings = Strings
|
||||||
|
{ data_ :: Ptr String_
|
||||||
|
, size :: CSize
|
||||||
|
}
|
||||||
|
|
||||||
|
instance Storable Strings where
|
||||||
|
sizeOf _ = #{size struct strings}
|
||||||
|
|
||||||
|
alignment _ = #{alignment struct strings}
|
||||||
|
|
||||||
|
peek pointer = do
|
||||||
|
data_ <- #{peek struct string, data} pointer
|
||||||
|
|
||||||
|
size <- #{peek struct string, size} pointer
|
||||||
|
|
||||||
|
return Strings{ data_, size }
|
||||||
|
|
||||||
|
poke pointer Strings{ data_, size } = do
|
||||||
|
#{poke struct string, data} pointer data_
|
||||||
|
|
||||||
|
#{poke struct string, size} pointer size
|
||||||
|
|
||||||
|
fromStrings :: Strings -> IO (Vector ByteString)
|
||||||
|
fromStrings Strings{ data_, size} = do
|
||||||
|
foreignPointer <- Foreign.newForeignPtr_ data_
|
||||||
|
|
||||||
|
let storableVector =
|
||||||
|
Vector.Storable.unsafeFromForeignPtr0 foreignPointer
|
||||||
|
(fromIntegral @CSize @Int size)
|
||||||
|
|
||||||
|
traverse fromString_ (Vector.convert storableVector)
|
||||||
|
|
||||||
|
toStrings :: Vector ByteString -> Managed Strings
|
||||||
|
toStrings vector = do
|
||||||
|
storableVector <- fmap Vector.convert (traverse toString_ vector)
|
||||||
|
|
||||||
|
data_ <- Managed.managed (Vector.Storable.unsafeWith storableVector)
|
||||||
|
|
||||||
|
let size = fromIntegral @Int @CSize (Vector.Storable.length storableVector)
|
||||||
|
|
||||||
|
return Strings{ data_, size }
|
||||||
|
|
||||||
|
foreign import ccall "freePathInfo" freePathInfo :: Ptr CPathInfo -> IO ()
|
||||||
|
|
||||||
|
{-| We don't use the original @ValidPathInfo@ Nix type. Rather, the C FFI
|
||||||
|
defines a @pathinfo@ struct that wraps a subset of what we need in a
|
||||||
|
C-compatible API
|
||||||
|
-}
|
||||||
|
data CPathInfo = CPathInfo
|
||||||
|
{ deriver :: String_
|
||||||
|
, narHash :: String_
|
||||||
|
, narSize :: CLong
|
||||||
|
, references :: Strings
|
||||||
|
, sigs :: Strings
|
||||||
|
}
|
||||||
|
|
||||||
|
instance Storable CPathInfo where
|
||||||
|
sizeOf _ = #{size struct PathInfo}
|
||||||
|
|
||||||
|
alignment _ = #{alignment struct PathInfo}
|
||||||
|
|
||||||
|
peek pointer = do
|
||||||
|
deriver <- #{peek struct PathInfo, deriver} pointer
|
||||||
|
|
||||||
|
narHash <- #{peek struct PathInfo, narHash} pointer
|
||||||
|
|
||||||
|
narSize <- #{peek struct PathInfo, narSize} pointer
|
||||||
|
|
||||||
|
references <- #{peek struct PathInfo, references} pointer
|
||||||
|
|
||||||
|
sigs <- #{peek struct PathInfo, sigs} pointer
|
||||||
|
|
||||||
|
return CPathInfo{ deriver, narHash, narSize, references, sigs }
|
||||||
|
|
||||||
|
poke pointer CPathInfo{ deriver, narHash, narSize, references, sigs } = do
|
||||||
|
#{poke struct PathInfo, deriver} pointer deriver
|
||||||
|
|
||||||
|
#{poke struct PathInfo, narHash} pointer narHash
|
||||||
|
|
||||||
|
#{poke struct PathInfo, narSize} pointer narSize
|
||||||
|
|
||||||
|
#{poke struct PathInfo, references} pointer references
|
||||||
|
|
||||||
|
#{poke struct PathInfo, sigs} pointer sigs
|
||||||
|
|
||||||
|
data PathInfo = PathInfo
|
||||||
|
{ deriver :: Maybe ByteString
|
||||||
|
, narHash :: ByteString
|
||||||
|
, narSize :: Word64
|
||||||
|
, references :: Vector ByteString
|
||||||
|
, sigs :: Vector ByteString
|
||||||
|
} deriving (Show)
|
||||||
|
|
||||||
|
fromCPathInfo :: CPathInfo -> IO PathInfo
|
||||||
|
fromCPathInfo CPathInfo{ deriver, narHash, narSize, references, sigs } = do
|
||||||
|
deriver_ <-
|
||||||
|
if data_ (deriver :: String_) == Foreign.nullPtr
|
||||||
|
then return Nothing
|
||||||
|
else fmap Just (fromString_ deriver)
|
||||||
|
|
||||||
|
narHash_ <- fromString_ narHash
|
||||||
|
|
||||||
|
references_ <- fromStrings references
|
||||||
|
|
||||||
|
sigs_ <- fromStrings sigs
|
||||||
|
|
||||||
|
return PathInfo
|
||||||
|
{ deriver = deriver_
|
||||||
|
, narHash = narHash_
|
||||||
|
, narSize = fromIntegral @CLong @Word64 narSize
|
||||||
|
, references = references_
|
||||||
|
, sigs = sigs_
|
||||||
|
}
|
||||||
|
|
||||||
|
foreign import ccall "getStoreDir" getStoreDir_ :: Ptr String_ -> IO ()
|
||||||
|
|
||||||
|
getStoreDir :: IO ByteString
|
||||||
|
getStoreDir =
|
||||||
|
Foreign.alloca \output -> do
|
||||||
|
let open = getStoreDir_ output
|
||||||
|
let close = freeString output
|
||||||
|
Exception.bracket_ open close do
|
||||||
|
string_ <- peek output
|
||||||
|
fromString_ string_
|
||||||
|
|
||||||
|
foreign import ccall "queryPathFromHashPart" queryPathFromHashPart_
|
||||||
|
:: CString -> Ptr String_ -> IO ()
|
||||||
|
|
||||||
|
queryPathFromHashPart :: ByteString -> IO (Maybe ByteString)
|
||||||
|
queryPathFromHashPart hashPart = do
|
||||||
|
ByteString.useAsCString hashPart \cHashPart -> do
|
||||||
|
Foreign.alloca \output -> do
|
||||||
|
let open = queryPathFromHashPart_ cHashPart output
|
||||||
|
let close = freeString output
|
||||||
|
Exception.bracket_ open close do
|
||||||
|
string_@String_{ data_} <- peek output
|
||||||
|
if data_ == Foreign.nullPtr
|
||||||
|
then return Nothing
|
||||||
|
else fmap Just (fromString_ string_)
|
||||||
|
|
||||||
|
foreign import ccall "queryPathInfo" queryPathInfo_
|
||||||
|
:: CString -> Ptr CPathInfo -> IO ()
|
||||||
|
|
||||||
|
queryPathInfo :: ByteString -> IO PathInfo
|
||||||
|
queryPathInfo storePath = do
|
||||||
|
ByteString.useAsCString storePath \cStorePath -> do
|
||||||
|
Foreign.alloca \output -> do
|
||||||
|
let open = queryPathInfo_ cStorePath output
|
||||||
|
let close = freePathInfo output
|
||||||
|
Exception.bracket_ open close do
|
||||||
|
cPathInfo <- peek output
|
||||||
|
fromCPathInfo cPathInfo
|
||||||
|
|
||||||
|
|
||||||
181
src/Options.hs
Normal file
181
src/Options.hs
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
{-# LANGUAGE ApplicativeDo #-}
|
||||||
|
{-# LANGUAGE BlockArguments #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
|
||||||
|
module Options where
|
||||||
|
|
||||||
|
import Control.Applicative (optional, (<|>))
|
||||||
|
import Data.String (IsString(..))
|
||||||
|
import Data.Void (Void)
|
||||||
|
import Network.Wai.Handler.Warp (HostPreference, Port)
|
||||||
|
import Options.Applicative (Parser, ParserInfo, ReadM)
|
||||||
|
import Text.Megaparsec (Parsec)
|
||||||
|
|
||||||
|
import qualified Options.Applicative as Options
|
||||||
|
import qualified Text.Megaparsec as Megaparsec
|
||||||
|
import qualified Text.Megaparsec.Char.Lexer as Lexer
|
||||||
|
|
||||||
|
data Socket
|
||||||
|
= TCP { host :: HostPreference, port :: Port }
|
||||||
|
| Unix { path :: FilePath }
|
||||||
|
|
||||||
|
data SSL = Disabled | Enabled { cert :: FilePath, key :: FilePath }
|
||||||
|
|
||||||
|
data Options = Options
|
||||||
|
{ priority :: Integer
|
||||||
|
, socket :: Socket
|
||||||
|
, ssl :: SSL
|
||||||
|
}
|
||||||
|
|
||||||
|
parseCert :: Parser FilePath
|
||||||
|
parseCert =
|
||||||
|
Options.strOption
|
||||||
|
( Options.long "ssl-cert"
|
||||||
|
<> Options.help "The path to the SSL certificate file"
|
||||||
|
<> Options.metavar "FILE"
|
||||||
|
)
|
||||||
|
|
||||||
|
parseKey :: Parser FilePath
|
||||||
|
parseKey =
|
||||||
|
Options.strOption
|
||||||
|
( Options.long "ssl-key"
|
||||||
|
<> Options.help "The path to the SSL key file"
|
||||||
|
<> Options.metavar "FILE"
|
||||||
|
)
|
||||||
|
|
||||||
|
parseSslEnabled :: Parser SSL
|
||||||
|
parseSslEnabled = do
|
||||||
|
-- [NOTE: enable-ssl]
|
||||||
|
--
|
||||||
|
-- We parse this for backwards compatibility with nix-serve, but ignore
|
||||||
|
-- it. Instead, we use the presence of the --ssl-key and --ssl-cert
|
||||||
|
-- options to detect whether SSL is enabled
|
||||||
|
Options.switch
|
||||||
|
( Options.long "enable-ssl"
|
||||||
|
<> Options.internal
|
||||||
|
)
|
||||||
|
|
||||||
|
cert <- parseCert
|
||||||
|
|
||||||
|
key <- parseKey
|
||||||
|
|
||||||
|
return Enabled{..}
|
||||||
|
|
||||||
|
parseSsl :: Parser SSL
|
||||||
|
parseSsl = parseSslEnabled <|> pure Disabled
|
||||||
|
|
||||||
|
parseTcp :: Parser Socket
|
||||||
|
parseTcp = do
|
||||||
|
host <- Options.strOption
|
||||||
|
( Options.long "host"
|
||||||
|
<> Options.help "The hostname to bind to"
|
||||||
|
<> Options.metavar "*|*4|!4|*6|!6|IPv4|IPv6"
|
||||||
|
-- The default host for warp is "*4", but we specify a default of
|
||||||
|
-- "*" for backwards compatibility with nix-serve, which defaults to
|
||||||
|
-- binding to any IP address. Also, binding to any IP address is
|
||||||
|
-- probably a better default anyway than binding to only IPv4
|
||||||
|
-- addresses.
|
||||||
|
<> Options.value "*"
|
||||||
|
<> Options.showDefaultWith (\_ -> "*")
|
||||||
|
)
|
||||||
|
|
||||||
|
port <- Options.option Options.auto
|
||||||
|
( Options.long "port"
|
||||||
|
<> Options.help "The port to bind to"
|
||||||
|
<> Options.metavar "0-65535"
|
||||||
|
-- This is also for backwards compatibility with nix-serve, which
|
||||||
|
-- defaults to port 5000.
|
||||||
|
<> Options.value 5000
|
||||||
|
)
|
||||||
|
|
||||||
|
return TCP{..}
|
||||||
|
|
||||||
|
parseUnix :: Parser Socket
|
||||||
|
parseUnix = do
|
||||||
|
path <- Options.strOption
|
||||||
|
( Options.long "socket"
|
||||||
|
<> Options.short 'S'
|
||||||
|
<> Options.help "The socket to bind to"
|
||||||
|
<> Options.metavar "PATH"
|
||||||
|
)
|
||||||
|
|
||||||
|
return Unix{..}
|
||||||
|
|
||||||
|
parseSocket :: Parser Socket
|
||||||
|
parseSocket = parseTcp <|> parseUnix
|
||||||
|
|
||||||
|
parsePriority :: Parser Integer
|
||||||
|
parsePriority =
|
||||||
|
Options.option Options.auto
|
||||||
|
( Options.long "priority"
|
||||||
|
<> Options.help "The priority of the cache (lower is higher priority)"
|
||||||
|
<> Options.metavar "INTEGER"
|
||||||
|
<> Options.value 30
|
||||||
|
)
|
||||||
|
|
||||||
|
parseOptions :: Parser Options
|
||||||
|
parseOptions = do
|
||||||
|
socket <- parseTcp <|> parseUnix
|
||||||
|
|
||||||
|
ssl <- parseSsl
|
||||||
|
|
||||||
|
priority <- parsePriority
|
||||||
|
|
||||||
|
return Options{..}
|
||||||
|
|
||||||
|
parseReader :: Parsec Void String a -> ReadM a
|
||||||
|
parseReader parser =
|
||||||
|
Options.eitherReader \string -> do
|
||||||
|
case Megaparsec.parse parser "(input)" string of
|
||||||
|
Left bundle -> Left (Megaparsec.errorBundlePretty bundle)
|
||||||
|
Right a -> Right a
|
||||||
|
|
||||||
|
-- This is only for backwards compatibility with nix-serve, which supports
|
||||||
|
-- the --listen option
|
||||||
|
parseListen :: Parser Options
|
||||||
|
parseListen = do
|
||||||
|
let parseTcpListen :: Parsec Void String Socket
|
||||||
|
parseTcpListen = do
|
||||||
|
host <- parseHost <|> pure "*"
|
||||||
|
|
||||||
|
_ <- ":"
|
||||||
|
|
||||||
|
port <- Lexer.decimal
|
||||||
|
|
||||||
|
-- See: [NOTE: enable-ssl]
|
||||||
|
_ <- optional ":ssl"
|
||||||
|
|
||||||
|
return TCP{..}
|
||||||
|
|
||||||
|
let parseUnixListen :: Parsec Void String Socket
|
||||||
|
parseUnixListen = do
|
||||||
|
path <- Megaparsec.takeRest
|
||||||
|
return Unix{..}
|
||||||
|
|
||||||
|
let parseSocketListen = parseTcpListen <|> parseUnixListen
|
||||||
|
|
||||||
|
ssl <- parseSsl
|
||||||
|
|
||||||
|
socket <- Options.option (parseReader parseSocketListen)
|
||||||
|
( Options.long "listen"
|
||||||
|
<> Options.short 'l'
|
||||||
|
<> Options.help "The TLS-enabled host and port to bind to"
|
||||||
|
<> Options.metavar "[HOST]:PORT|UNIX_SOCKET"
|
||||||
|
<> Options.hidden
|
||||||
|
)
|
||||||
|
|
||||||
|
priority <- parsePriority
|
||||||
|
|
||||||
|
return Options{..}
|
||||||
|
|
||||||
|
parserInfo :: ParserInfo Options
|
||||||
|
parserInfo =
|
||||||
|
Options.info
|
||||||
|
(Options.helper <*> (parseOptions <|> parseListen))
|
||||||
|
(Options.progDesc "Serve the current Nix store as a binary cache")
|
||||||
|
|
||||||
|
parseHost :: Parsec Void String HostPreference
|
||||||
|
parseHost = do
|
||||||
|
string <- Megaparsec.takeWhileP Nothing (/= ':')
|
||||||
|
return (fromString string)
|
||||||
Loading…
x
Reference in New Issue
Block a user