diff --git a/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix b/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix new file mode 100644 index 00000000000..3ec74458cd2 --- /dev/null +++ b/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix @@ -0,0 +1,116 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.hardware.sane.brscan4; + + netDeviceList = attrValues cfg.netDevices; + + etcFiles = pkgs.callPackage ./brscan4_etc_files.nix { netDevices = netDeviceList; }; + + netDeviceOpts = { name, config, ... }: { + + options = { + + name = mkOption { + type = types.str; + description = '' + The friendly name you give to the network device. If undefined, + the name of attribute will be used. + ''; + + example = literalExample "office1"; + }; + + model = mkOption { + type = types.str; + description = '' + The model of the network device. + ''; + + example = literalExample "MFC-7860DW"; + }; + + ip = mkOption { + type = with types; nullOr str; + default = null; + description = '' + The ip address of the device. If undefined, you will have to + provide a nodename. + ''; + + example = literalExample "192.168.1.2"; + }; + + nodename = mkOption { + type = with types; nullOr str; + default = null; + description = '' + The node name of the device. If undefined, you will have to + provide an ip. + ''; + + example = literalExample "BRW0080927AFBCE"; + }; + + }; + + + config = + { name = mkDefault name; + }; + }; + +in + +{ + options = { + + hardware.sane.brscan4.enable = + mkEnableOption "Brother's brscan4 scan backend" // { + description = '' + When enabled, will automatically register the "brscan4" sane + backend and bring configuration files to their expected location. + ''; + }; + + hardware.sane.brscan4.netDevices = mkOption { + default = {}; + example = + { office1 = { model = "MFC-7860DW"; ip = "192.168.1.2"; }; + office2 = { model = "MFC-7860DW"; nodename = "BRW0080927AFBCE"; }; + }; + type = types.loaOf types.optionSet; + description = '' + The list of network devices that will be registered against the brscan4 + sane backend. + ''; + options = [ netDeviceOpts ]; + }; + }; + + config = mkIf (config.hardware.sane.enable && cfg.enable) { + + hardware.sane.extraBackends = [ + pkgs.brscan4 + ]; + + environment.etc = singleton { + target = "opt/brother/scanner/brscan4"; + source = "${etcFiles}/etc/opt/brother/scanner/brscan4"; + }; + + assertions = [ + { assertion = all (x: !(null != x.ip && null != x.nodename)) netDeviceList; + + message = '' + When describing a network device as part of the attribute list + `hardware.sane.brscan4.netDevices`, only one of its `ip` or `nodename` + attribute should be specified, not both! + ''; + } + ]; + + }; +} \ No newline at end of file diff --git a/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix b/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix new file mode 100644 index 00000000000..bd114f0d2cc --- /dev/null +++ b/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix @@ -0,0 +1,71 @@ +{ stdenv, lib, brscan4, netDevices ? [] }: + +/* + +Testing +------- + +No net devices: + +~~~ +nix-shell -E 'with import { }; brscan4-etc-files' +~~~ + +Two net devices: + +~~~ +nix-shell -E 'with import { }; brscan4-etc-files.override{netDevices=[{name="a"; model="MFC-7860DW"; nodename="BRW0080927AFBCE";} {name="b"; model="MFC-7860DW"; ip="192.168.1.2";}];}' +~~~ + +*/ + +with lib; + +let + + addNetDev = nd: '' + brsaneconfig4 -a \ + name="${nd.name}" \ + model="${nd.model}" \ + ${if (hasAttr "nodename" nd && nd.nodename != null) then + ''nodename="${nd.nodename}"'' else + ''ip="${nd.ip}"''}''; + addAllNetDev = xs: concatStringsSep "\n" (map addNetDev xs); +in + +stdenv.mkDerivation rec { + + name = "brscan4-etc-files-0.4.3-3"; + src = "${brscan4}/opt/brother/scanner/brscan4"; + + nativeBuildInputs = [ brscan4 ]; + + configurePhase = ":"; + + buildPhase = '' + TARGET_DIR="$out/etc/opt/brother/scanner/brscan4" + mkdir -p "$TARGET_DIR" + cp -rp "./models4" "$TARGET_DIR" + cp -rp "./Brsane4.ini" "$TARGET_DIR" + cp -rp "./brsanenetdevice4.cfg" "$TARGET_DIR" + + export BRSANENETDEVICE4_CFG_FILENAME="$TARGET_DIR/brsanenetdevice4.cfg" + + printf '${addAllNetDev netDevices}\n' + + ${addAllNetDev netDevices} + ''; + + installPhase = ":"; + + dontStrip = true; + dontPatchELF = true; + + meta = { + description = "Brother brscan4 sane backend driver etc files"; + homepage = http://www.brother.com; + platforms = stdenv.lib.platforms.linux; + license = stdenv.lib.licenses.unfree; + maintainers = with stdenv.lib.maintainers; [ jraygauthier ]; + }; +} diff --git a/pkgs/applications/graphics/sane/backends/brscan4/default.nix b/pkgs/applications/graphics/sane/backends/brscan4/default.nix new file mode 100644 index 00000000000..7b22e88bb84 --- /dev/null +++ b/pkgs/applications/graphics/sane/backends/brscan4/default.nix @@ -0,0 +1,97 @@ +{ stdenv, fetchurl, callPackage, patchelf, makeWrapper, coreutils, libusb }: + +/* + + +*/ + +let + + myPatchElf = file: with stdenv.lib; '' + patchelf --set-interpreter \ + ${stdenv.glibc}/lib/ld-linux${optionalString stdenv.is64bit "-x86-64"}.so.2 \ + ${file} + ''; + + udevRules = callPackage ./udev_rules_type1.nix {}; + +in + +stdenv.mkDerivation rec { + + name = "brscan4-0.4.3-3"; + src = fetchurl { + url = "http://download.brother.com/welcome/dlf006645/${name}.amd64.deb"; + sha256 = "1nccyjl0b195pn6ya4q0zijb075q8r31v9z9a0hfzipfyvcj57n2"; + }; + + unpackPhase = '' + ar x $src + tar xfvz data.tar.gz + ''; + + nativeBuildInputs = [ makeWrapper patchelf coreutils udevRules ]; + buildInputs = [ libusb ]; + buildPhase = ":"; + + + patchPhase = '' + ${myPatchElf "opt/brother/scanner/brscan4/brsaneconfig4"} + + RPATH=${libusb}/lib + for a in usr/lib64/sane/*.so*; do + if ! test -L $a; then + patchelf --set-rpath $RPATH $a + fi + done + ''; + + installPhase = '' + + PATH_TO_BRSCAN4="opt/brother/scanner/brscan4" + mkdir -p $out/$PATH_TO_BRSCAN4 + cp -rp $PATH_TO_BRSCAN4/* $out/$PATH_TO_BRSCAN4 + mkdir -p $out/lib/sane + cp -rp usr/lib64/sane/* $out/lib/sane + + # Symbolic links were absolute. Fix them so that they point to $out. + pushd "$out/lib/sane" > /dev/null + for a in *.so*; do + if test -L $a; then + fixedTargetFileName="$(basename $(readlink $a))" + unlink "$a" + ln -s -T "$fixedTargetFileName" "$a" + fi + done + popd > /dev/null + + # Generate an LD_PRELOAD wrapper to redirect execvp(), open() and open64() + # calls to `/opt/brother/scanner/brscan4`. + preload=$out/libexec/brother/scanner/brscan4/libpreload.so + mkdir -p $(dirname $preload) + gcc -shared ${./preload.c} -o $preload -ldl -DOUT=\"$out\" -fPIC + + makeWrapper \ + "$out/$PATH_TO_BRSCAN4/brsaneconfig4" \ + "$out/bin/brsaneconfig4" \ + --set LD_PRELOAD $preload + + mkdir -p $out/etc/sane.d + echo "brother4" > $out/etc/sane.d/dll.conf + + mkdir -p $out/etc/udev/rules.d + cp -p ${udevRules}/etc/udev/rules.d/*.rules \ + $out/etc/udev/rules.d + ''; + + dontStrip = true; + dontPatchELF = true; + + meta = { + description = "Brother brscan4 sane backend driver"; + homepage = http://www.brother.com; + platforms = stdenv.lib.platforms.linux; + license = stdenv.lib.licenses.unfree; + maintainers = with stdenv.lib.maintainers; [ jraygauthier ]; + }; +} diff --git a/pkgs/applications/graphics/sane/backends/brscan4/preload.c b/pkgs/applications/graphics/sane/backends/brscan4/preload.c new file mode 100644 index 00000000000..01616277093 --- /dev/null +++ b/pkgs/applications/graphics/sane/backends/brscan4/preload.c @@ -0,0 +1,170 @@ +/* Brgen4 search for configuration under `/etc/opt/brother/scanner/brscan4`. This + LD_PRELOAD library intercepts execvp(), open and open64 calls to redirect them to + the corresponding location in $out. Also support specifying an alternate + file name for `brsanenetdevice4.cfg` which otherwise is invariable + created at `/etc/opt/brother/scanner/brscan4`*/ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char origDir [] = "/etc/opt/brother/scanner/brscan4"; +char realDir [] = OUT "/opt/brother/scanner/brscan4"; + +char devCfgFileNameEnvVar [] = "BRSANENETDEVICE4_CFG_FILENAME"; +char devCfgFileName [] = "/etc/opt/brother/scanner/brscan4//brsanenetdevice4.cfg"; + +const char * rewrite(const char * path, char * buf) +{ + if (strncmp(path, devCfgFileName, sizeof(devCfgFileName)) == 0) { + + const char* newCfgFileName = getenv(devCfgFileNameEnvVar); + if (!newCfgFileName) return path; + + if (snprintf(buf, PATH_MAX, "%s", newCfgFileName) >= PATH_MAX) + abort(); + return buf; + } + + if (strncmp(path, origDir, sizeof(origDir) - 1) != 0) return path; + if (snprintf(buf, PATH_MAX, "%s%s", realDir, path + sizeof(origDir) - 1) >= PATH_MAX) + abort(); + return buf; +} + +const char* findAndReplaceFirstOccurence(const char* inStr, const char* subStr, + const char* replaceStr, + char* buf, unsigned maxBuf) +{ + const char* foundStr = strstr(inStr, subStr); + if (!foundStr) + return inStr; + + const unsigned inStrLen = strlen(inStr); + const unsigned subStrLen = strlen(subStr); + const unsigned replaceStrLen = strlen(replaceStr); + + const unsigned precedingStrLen = foundStr - inStr; + if (precedingStrLen + 1 > maxBuf) + return NULL; + + const unsigned followingStrPos = precedingStrLen + subStrLen; + const unsigned followingStrLen = inStrLen - followingStrPos; + + strncpy(buf, inStr, precedingStrLen); + unsigned outLength = precedingStrLen; + + if (outLength + replaceStrLen + 1 > maxBuf) + return NULL; + + strncpy(buf + outLength, replaceStr, replaceStrLen); + outLength += replaceStrLen; + + if (outLength + followingStrLen + 1 > maxBuf) + return NULL; + + strncpy(buf + outLength, inStr + followingStrPos, followingStrLen); + outLength += followingStrLen; + + buf[outLength] = '\0'; + + return buf; +} + +const char* rewriteSystemCall(const char* command, char* buf, unsigned maxBuf) +{ + + const char* foundStr = strstr(command, devCfgFileName); + if (!foundStr) + return command; + + const char* replaceStr = getenv(devCfgFileNameEnvVar); + if (!replaceStr) return command; + + const char* result = + findAndReplaceFirstOccurence(command, devCfgFileName, replaceStr, buf, maxBuf); + + if (!result) + abort(); + + return result; +} + +int execvp(const char * path, char * const argv[]) +{ + int (*_execvp) (const char *, char * const argv[]) = dlsym(RTLD_NEXT, "execvp"); + char buf[PATH_MAX]; + return _execvp(rewrite(path, buf), argv); +} + + +int open(const char *path, int flags, ...) +{ + char buf[PATH_MAX]; + int (*_open) (const char *, int, mode_t) = dlsym(RTLD_NEXT, "open"); + mode_t mode = 0; + if (flags & O_CREAT) { + va_list ap; + va_start(ap, flags); + mode = va_arg(ap, mode_t); + va_end(ap); + } + return _open(rewrite(path, buf), flags, mode); +} + +int open64(const char *path, int flags, ...) +{ + char buf[PATH_MAX]; + int (*_open64) (const char *, int, mode_t) = dlsym(RTLD_NEXT, "open64"); + mode_t mode = 0; + if (flags & O_CREAT) { + va_list ap; + va_start(ap, flags); + mode = va_arg(ap, mode_t); + va_end(ap); + } + return _open64(rewrite(path, buf), flags, mode); +} + +FILE* fopen(const char* path, const char* mode) +{ + char buf[PATH_MAX]; + FILE* (*_fopen) (const char*, const char*) = dlsym(RTLD_NEXT, "fopen"); + + return _fopen(rewrite(path, buf), mode); +} + +FILE *fopen64(const char *path, const char *mode) +{ + char buf[PATH_MAX]; + FILE* (*_fopen64) (const char*, const char*) = dlsym(RTLD_NEXT, "fopen64"); + + return _fopen64(rewrite(path, buf), mode); +} + +DIR* opendir(const char* path) +{ + char buf[PATH_MAX]; + DIR* (*_opendir) (const char*) = dlsym(RTLD_NEXT, "opendir"); + + return _opendir(rewrite(path, buf)); +} + +#define SYSTEM_CMD_MAX 512 + +int system(const char *command) +{ + char buf[SYSTEM_CMD_MAX]; + int (*_system) (const char*) = dlsym(RTLD_NEXT, "system"); + + const char* newCommand = rewriteSystemCall(command, buf, SYSTEM_CMD_MAX); + return _system(newCommand); +} diff --git a/pkgs/applications/graphics/sane/backends/brscan4/udev_rules_type1.nix b/pkgs/applications/graphics/sane/backends/brscan4/udev_rules_type1.nix new file mode 100644 index 00000000000..873240e81fc --- /dev/null +++ b/pkgs/applications/graphics/sane/backends/brscan4/udev_rules_type1.nix @@ -0,0 +1,60 @@ +{ stdenv, fetchurl, libsaneUDevRuleNumber ? "49"}: + + +stdenv.mkDerivation rec { + + name = "brother-udev-rule-type1-1.0.0-1"; + + src = fetchurl { + url = "http://download.brother.com/welcome/dlf006654/${name}.all.deb"; + sha256 = "0i0x5jw135pli4jl9mgnr5n2rrdvml57nw84yq2999r4frza53xi"; + }; + + buildInputs = [ ]; + + unpackPhase = '' + ar x $src + tar xfvz data.tar.gz + ''; + + /* + Fix the following error: + + ~~~ + invalid rule 49-brother-libsane-type1.rules + unknown key 'SYSFS{idVendor}' + ~~~ + + Apparently the udev rules syntax has change and the SYSFS key has to + be changed to ATTR. + + See: + + - + - + */ + patchPhase = '' + sed -i -e s/SYSFS/ATTR/g opt/brother/scanner/udev-rules/type1/*.rules + ''; + + + buildPhase = ":"; + + installPhase = '' + mkdir -p $out/etc/udev/rules.d + cp opt/brother/scanner/udev-rules/type1/NN-brother-mfp-type1.rules \ + $out/etc/udev/rules.d/${libsaneUDevRuleNumber}-brother-libsane-type1.rules + chmod 644 $out/etc/udev/rules.d/${libsaneUDevRuleNumber}-brother-libsane-type1.rules + ''; + + dontStrip = true; + dontPatchELF = true; + + meta = { + description = "Brother type1 scanners udev rules"; + homepage = http://www.brother.com; + platforms = stdenv.lib.platforms.linux; + license = stdenv.lib.licenses.unfree; + maintainers = with stdenv.lib.maintainers; [ jraygauthier ]; + }; +} \ No newline at end of file diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 586248d04ee..2b922f330bd 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -16436,6 +16436,8 @@ in snapscanFirmware = config.sane.snapscanFirmware or null; }; + brscan4 = callPackage ../applications/graphics/sane/backends/brscan4 { }; + mkSaneConfig = callPackage ../applications/graphics/sane/config.nix { }; sane-frontends = callPackage ../applications/graphics/sane/frontends.nix { };