prl-tools: 12.2.1-41615 -> 17.1.4-51567

Install Parallel Tools updated for version 17 of Parallels for macOS. This
fixes clipboard sharing, so that copy and paste works between the host
macOS and the guest NixOS VM. Support for guests on M1 Apple Silicon-based
Macs (aarch64-linux) is also added.

Co-authored-by: Paul Smith <paulsmith@gmail.com>
Co-authored-by: Weijia Wang <9713184+wegank@users.noreply.github.com>
This commit is contained in:
Kirill A. Korinsky 2022-07-09 14:43:27 +02:00
parent 0a9f100d77
commit f41fc22111
No known key found for this signature in database
GPG key ID: 98D8D9867759226E
4 changed files with 238 additions and 128 deletions

View file

@ -34,7 +34,8 @@ in
package = mkOption {
type = types.nullOr types.package;
default = config.boot.kernelPackages.prl-tools;
defaultText = literalExpression "config.boot.kernelPackages.prl-tools";
defaultText = "config.boot.kernelPackages.prl-tools";
example = literalExpression "config.boot.kernelPackages.prl-tools";
description = ''
Defines which package to use for prl-tools. Override to change the version.
'';
@ -44,27 +45,6 @@ in
};
config = mkIf config.hardware.parallels.enable {
services.xserver = {
drivers = singleton
{ name = "prlvideo"; modules = [ prl-tools ]; };
screenSection = ''
Option "NoMTRR"
'';
config = ''
Section "InputClass"
Identifier "prlmouse"
MatchIsPointer "on"
MatchTag "prlmouse"
Driver "prlmouse"
EndSection
'';
};
hardware.opengl.package = prl-tools;
hardware.opengl.package32 = pkgs.pkgsi686Linux.linuxPackages.prl-tools.override { libsOnly = true; kernel = null; };
hardware.opengl.setLdLibraryPath = true;
services.udev.packages = [ prl-tools ];
@ -72,37 +52,44 @@ in
boot.extraModulePackages = [ prl-tools ];
boot.kernelModules = [ "prl_tg" "prl_eth" "prl_fs" "prl_fs_freeze" ];
boot.kernelModules = [ "prl_fs" "prl_fs_freeze" "prl_tg" ]
++ optional (pkgs.stdenv.hostPlatform.system == "aarch64-linux") "prl_notifier";
services.timesyncd.enable = false;
systemd.services.prltoolsd = {
description = "Parallels Tools' service";
description = "Parallels Tools Service";
wantedBy = [ "multi-user.target" ];
path = [ prl-tools ];
serviceConfig = {
ExecStart = "${prl-tools}/bin/prltoolsd -f";
PIDFile = "/var/run/prltoolsd.pid";
WorkingDirectory = "${prl-tools}/bin";
};
};
systemd.services.prlfsmountd = mkIf config.hardware.parallels.autoMountShares {
description = "Parallels Shared Folders Daemon";
description = "Parallels Guest File System Sharing Tool";
wantedBy = [ "multi-user.target" ];
path = [ prl-tools ];
serviceConfig = rec {
ExecStart = "${prl-tools}/sbin/prlfsmountd ${PIDFile}";
ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /media";
ExecStopPost = "${prl-tools}/sbin/prlfsmountd -u";
PIDFile = "/run/prlfsmountd.pid";
WorkingDirectory = "${prl-tools}/bin";
};
};
systemd.services.prlshprint = {
description = "Parallels Shared Printer Tool";
description = "Parallels Printing Tool";
wantedBy = [ "multi-user.target" ];
bindsTo = [ "cups.service" ];
path = [ prl-tools ];
serviceConfig = {
Type = "forking";
ExecStart = "${prl-tools}/bin/prlshprint";
WorkingDirectory = "${prl-tools}/bin";
};
};
@ -110,43 +97,47 @@ in
prlcc = {
description = "Parallels Control Center";
wantedBy = [ "graphical-session.target" ];
path = [ prl-tools ];
serviceConfig = {
ExecStart = "${prl-tools}/bin/prlcc";
WorkingDirectory = "${prl-tools}/bin";
};
};
prldnd = {
description = "Parallels Control Center";
description = "Parallels Drag And Drop Tool";
wantedBy = [ "graphical-session.target" ];
path = [ prl-tools ];
serviceConfig = {
ExecStart = "${prl-tools}/bin/prldnd";
};
};
prl_wmouse_d = {
description = "Parallels Walking Mouse Daemon";
wantedBy = [ "graphical-session.target" ];
serviceConfig = {
ExecStart = "${prl-tools}/bin/prl_wmouse_d";
WorkingDirectory = "${prl-tools}/bin";
};
};
prlcp = {
description = "Parallels CopyPaste Tool";
description = "Parallels Copy Paste Tool";
wantedBy = [ "graphical-session.target" ];
path = [ prl-tools ];
serviceConfig = {
ExecStart = "${prl-tools}/bin/prlcp";
Restart = "always";
WorkingDirectory = "${prl-tools}/bin";
};
};
prlsga = {
description = "Parallels Shared Guest Applications Tool";
wantedBy = [ "graphical-session.target" ];
path = [ prl-tools ];
serviceConfig = {
ExecStart = "${prl-tools}/bin/prlsga";
WorkingDirectory = "${prl-tools}/bin";
};
};
prlshprof = {
description = "Parallels Shared Profile Tool";
wantedBy = [ "graphical-session.target" ];
path = [ prl-tools ];
serviceConfig = {
ExecStart = "${prl-tools}/bin/prlshprof";
WorkingDirectory = "${prl-tools}/bin";
};
};
};

View file

@ -1,50 +1,59 @@
{ stdenv, lib, makeWrapper, p7zip
, gawk, util-linux, xorg, glib, dbus-glib, zlib
, gawk, util-linux, xorg, glib, dbus-glib, zlib, bbe, bash, timetrap, netcat, cups
, kernel ? null, libsOnly ? false
, undmg, fetchurl
, fetchurl, undmg, perl, autoPatchelfHook
}:
assert (!libsOnly) -> kernel != null;
assert lib.elem stdenv.hostPlatform.system [ "x86_64-linux" "i686-linux" "aarch64-linux" ];
let xorgFullVer = lib.getVersion xorg.xorgserver;
xorgVer = lib.versions.majorMinor xorgFullVer;
x64 = if stdenv.hostPlatform.system == "x86_64-linux" then true
else if stdenv.hostPlatform.system == "i686-linux" then false
else throw "Parallels Tools for Linux only support {x86-64,i686}-linux targets";
in
stdenv.mkDerivation rec {
version = "${prl_major}.2.1-41615";
prl_major = "12";
version = "17.1.4-51567";
pname = "prl-tools";
# We download the full distribution to extract prl-tools-lin.iso from
# => ${dmg}/Parallels\ Desktop.app/Contents/Resources/Tools/prl-tools-lin.iso
src = fetchurl {
url = "https://download.parallels.com/desktop/v${prl_major}/${version}/ParallelsDesktop-${version}.dmg";
sha256 = "1jwzwif69qlhmfky9kigjaxpxfj0lyrl1iyrpqy4iwqvajdgbbym";
url = "https://download.parallels.com/desktop/v${lib.versions.major version}/${version}/ParallelsDesktop-${version}.dmg";
sha256 = "sha256-gjLxQOTFuVghv1Bj+zfbNW97q1IN2rurSnPQi13gzRA=";
};
hardeningDisable = [ "pic" "format" ];
# also maybe python2 to generate xorg.conf
nativeBuildInputs = [ p7zip undmg ] ++ lib.optionals (!libsOnly) [ makeWrapper ] ++ kernel.moduleBuildDependencies;
nativeBuildInputs = [ p7zip undmg perl bbe autoPatchelfHook ]
++ lib.optionals (!libsOnly) [ makeWrapper ] ++ kernel.moduleBuildDependencies;
buildInputs = with xorg; [ libXrandr libXext libX11 libXcomposite libXinerama ]
++ lib.optionals (!libsOnly) [ libXi glib dbus-glib zlib ];
runtimeDependencies = [ glib xorg.libXrandr ];
inherit libsOnly;
unpackPhase = ''
undmg "${src}"
export sourceRoot=prl-tools-build
7z x "Parallels Desktop.app/Contents/Resources/Tools/prl-tools-lin.iso" -o$sourceRoot
7z x "Parallels Desktop.app/Contents/Resources/Tools/prl-tools-lin${lib.optionalString stdenv.isAarch64 "-arm"}.iso" -o$sourceRoot
if test -z "$libsOnly"; then
( cd $sourceRoot/kmods; tar -xaf prl_mod.tar.gz )
fi
( cd $sourceRoot/tools; tar -xaf prltools${if x64 then ".x64" else ""}.tar.gz )
'';
kernelVersion = if libsOnly then "" else lib.getName kernel.name;
kernelDir = if libsOnly then "" else "${kernel.dev}/lib/modules/${kernelVersion}";
scriptPath = lib.concatStringsSep ":" (lib.optionals (!libsOnly) [ "${util-linux}/bin" "${gawk}/bin" ]);
patches = lib.optionals (lib.versionAtLeast kernel.version "5.18") [ ./prl-tools.patch ];
kernelVersion = lib.optionalString (!libsOnly) kernel.modDirVersion;
kernelDir = lib.optionalString (!libsOnly) "${kernel.dev}/lib/modules/${kernelVersion}";
libPath = lib.concatStringsSep ":" [ "${glib.out}/lib" "${xorg.libXrandr}/lib" ];
scriptPath = lib.concatStringsSep ":" (lib.optionals (!libsOnly) [
"${util-linux}/bin"
"${gawk}/bin"
"${bash}/bin"
"${timetrap}/bin"
"${netcat}/bin"
"${cups}/sbin"
]);
buildPhase = ''
if test -z "$libsOnly"; then
@ -57,112 +66,80 @@ stdenv.mkDerivation rec {
SRC=$kernelDir/build \
KVER=$kernelVersion
)
# Xorg config (maybe would be useful for other versions)
#python2 installer/xserver-config.py xorg ${xorgVer} /dev/null parallels.conf
fi
'';
libPath = with xorg;
lib.makeLibraryPath ([ stdenv.cc.cc libXrandr libXext libX11 libXcomposite libXinerama ]
++ lib.optionals (!libsOnly) [ libXi glib dbus-glib zlib ]);
installPhase = ''
if test -z "$libsOnly"; then
( # kernel modules
cd kmods
mkdir -p $out/lib/modules/${kernelVersion}/extra
cp prl_eth/pvmnet/prl_eth.ko $out/lib/modules/${kernelVersion}/extra
cp prl_tg/Toolgate/Guest/Linux/prl_tg/prl_tg.ko $out/lib/modules/${kernelVersion}/extra
cp prl_fs/SharedFolders/Guest/Linux/prl_fs/prl_fs.ko $out/lib/modules/${kernelVersion}/extra
cp prl_fs_freeze/Snapshot/Guest/Linux/prl_freeze/prl_fs_freeze.ko $out/lib/modules/${kernelVersion}/extra
cp prl_tg/Toolgate/Guest/Linux/prl_tg/prl_tg.ko $out/lib/modules/${kernelVersion}/extra
${lib.optionalString stdenv.isAarch64
"cp prl_notifier/Installation/lnx/prl_notifier/prl_notifier.ko $out/lib/modules/${kernelVersion}/extra"}
)
fi
( # tools
cd tools
cd tools/tools${if stdenv.isAarch64 then "-arm64" else if stdenv.isx86_64 then "64" else "32"}
mkdir -p $out/lib
if test -z "$libsOnly"; then
# prltoolsd contains hardcoded /bin/bash path
# we're lucky because it uses only -c command
# => replace to /bin/sh
bbe -e "s:/bin/bash:/bin/sh\x00\x00:" -o bin/prltoolsd.tmp bin/prltoolsd
rm -f bin/prltoolsd
mv bin/prltoolsd.tmp bin/prltoolsd
# install binaries
for i in bin/* sbin/prl_nettool sbin/prl_snapshot; do
# also patch binaries to replace /usr/bin/XXX to XXX
# here a two possible cases:
# 1. it is uses as null terminated string and should be truncated by null;
# 2. it is uses inside shell script and should be truncated by space.
for p in bin/* sbin/prl_nettool sbin/prl_snapshot sbin/prlfsmountd; do
p=$(basename $p)
bbe -e "s:/usr/bin/$p\x00:./$p\x00\x00\x00\x00\x00\x00\x00\x00:" -o $i.tmp $i
bbe -e "s:/usr/sbin/$p\x00:./$p\x00\x00\x00\x00\x00\x00\x00\x00 :" -o $i $i.tmp
bbe -e "s:/usr/bin/$p:$p :" -o $i.tmp $i
bbe -e "s:/usr/sbin/$p:$p :" -o $i $i.tmp
done
install -Dm755 $i $out/$i
done
# other binaries
for i in xorg.7.1/usr/bin/*; do
cp $i $out/bin
install -Dm755 ../../tools/prlfsmountd.sh $out/sbin/prlfsmountd
for f in $out/bin/* $out/sbin/*; do
wrapProgram $f \
--prefix LD_LIBRARY_PATH ':' "$libPath" \
--prefix PATH ':' "$scriptPath"
done
for i in $out/bin/* $out/sbin/*; do
patchelf \
--interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" \
--set-rpath "$out/lib:$libPath" \
$i || true
done
mkdir -p $out/bin
install -Dm755 ../installer/prlfsmountd.sh $out/sbin/prlfsmountd
wrapProgram $out/sbin/prlfsmountd \
--prefix PATH ':' "$scriptPath"
for i in lib/*.a; do
for i in lib/libPrl*.0.0; do
cp $i $out/lib
ln -s $out/$i $out/''${i%.0.0}
done
for i in xorg.7.1/usr/lib/libprl_wmouse_watcher.*; do
cp $i $out/lib
done
mkdir -p $out/share/man/man8
install -Dm644 ../mount.prl_fs.8 $out/share/man/man8
mkdir -p $out/lib/udev/rules.d
for i in *.rules; do
sed 's,/bin/bash,${stdenv.shell},g' $i > $out/lib/udev/rules.d/$i
done
substituteInPlace ../99prltoolsd-hibernate \
--replace "/bin/bash" "${bash}/bin/bash"
(
cd xorg.${xorgVer}
# Install the X modules.
(
cd x-server/modules
for i in */*; do
install -Dm755 $i $out/lib/xorg/modules/$i
done
)
(
cd usr/lib
libGLXname=$(echo libglx.so*)
install -Dm755 $libGLXname $out/lib/xorg/modules/extensions/$libGLXname
ln -s $libGLXname $out/lib/xorg/modules/extensions/libglx.so
ln -s $libGLXname $out/lib/xorg/modules/extensions/libglx.so.1
)
)
mkdir -p $out/etc/pm/sleep.d
install -Dm644 ../99prltoolsd-hibernate $out/etc/pm/sleep.d
fi
for i in xorg.7.1/usr/lib/libGL.*; do
cp $i $out/lib
done
cd $out
find -name \*.so\* -type f -exec \
patchelf --set-rpath "$out/lib:$libPath" {} \;
cd lib
libGLname=$(echo libGL.so*)
ln -s $libGLname libGL.so
ln -s $libGLname libGL.so.1
)
'';
dontStrip = true;
dontPatchELF = true;
meta = with lib; {
description = "Parallels Tools for Linux guests";
homepage = "https://parallels.com";
platforms = [ "i686-linux" "x86_64-linux" ];
platforms = platforms.linux;
license = licenses.unfree;
# I was making this package blindly and requesting testing from the real user,
# so I can't even test it by myself and won't provide future updates.
maintainers = with maintainers; [ abbradar ];
maintainers = with maintainers; [ catap wegank ];
};
}

View file

@ -0,0 +1,143 @@
diff -puNr prl-tools-build/kmods/prl_tg/Toolgate/Guest/Linux/prl_tg/prltg.c prl-tools-build/kmods/prl_tg/Toolgate/Guest/Linux/prl_tg/prltg.c
--- prl-tools-build/kmods/prl_tg/Toolgate/Guest/Linux/prl_tg/prltg.c
+++ prl-tools-build/kmods/prl_tg/Toolgate/Guest/Linux/prl_tg/prltg.c
@@ -382,7 +382,7 @@ static int prl_tg_initialize(struct tg_d
}
#endif
/* Set DMA ability. Only lower 4G is possible to address */
- rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
+ rc = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));
if (rc) {
printk(KERN_ERR "no usable DMA configuration\n");
goto err_out;
diff -puNr prl-tools-build/kmods/prl_tg/Toolgate/Guest/Linux/prl_tg/prltg_call.c prl-tools-build/kmods/prl_tg/Toolgate/Guest/Linux/prl_tg/prltg_call.c
--- prl-tools-build/kmods/prl_tg/Toolgate/Guest/Linux/prl_tg/prltg_call.c
+++ prl-tools-build/kmods/prl_tg/Toolgate/Guest/Linux/prl_tg/prltg_call.c
@@ -76,7 +76,7 @@ static int tg_req_map_internal(struct TG
uple->p[i] = vmalloc_to_page(mem);
page_cache_get(uple->p[i]);
- dst->RequestPages[i] = pci_map_page(pdev, uple->p[i], 0, PAGE_SIZE, DMA_BIDIRECTIONAL) >> PAGE_SHIFT;
+ dst->RequestPages[i] = dma_map_page(&pdev->dev, uple->p[i], 0, PAGE_SIZE, DMA_BIDIRECTIONAL) >> PAGE_SHIFT;
if (!dst->RequestPages[i]) {
page_cache_release(uple->p[i]);
goto err;
@@ -88,7 +88,7 @@ static int tg_req_map_internal(struct TG
err:
for (i = 0; i < uple->count; i++) {
- pci_unmap_page(pdev, dst->RequestPages[i] << PAGE_SHIFT, PAGE_SIZE, DMA_BIDIRECTIONAL);
+ dma_unmap_page(&pdev->dev, dst->RequestPages[i] << PAGE_SHIFT, PAGE_SIZE, DMA_BIDIRECTIONAL);
page_cache_release(uple->p[i]);
}
kfree(uple);
@@ -129,7 +129,7 @@ static TG_PAGED_BUFFER *tg_req_map_user_
pfn = (u64 *)dbuf - 1;
for (; npages > 0; npages--, mapped++) {
- dma_addr_t addr = pci_map_page(pdev, uple->p[npages-1], 0, PAGE_SIZE, DMA_BIDIRECTIONAL);
+ dma_addr_t addr = dma_map_page(&pdev->dev, uple->p[npages-1], 0, PAGE_SIZE, DMA_BIDIRECTIONAL);
if (!addr) {
DPRINTK("[3] %d < %d \n", got, npages);
@@ -144,7 +144,7 @@ static TG_PAGED_BUFFER *tg_req_map_user_
err_unmap:
for (i = 0; i < mapped; i++, pfn++)
- pci_unmap_page(pdev, *pfn << PAGE_SHIFT, PAGE_SIZE, DMA_BIDIRECTIONAL);
+ dma_unmap_page(&pdev->dev, *pfn << PAGE_SHIFT, PAGE_SIZE, DMA_BIDIRECTIONAL);
err_put:
for(i = 0; i < got; i++)
@@ -176,7 +176,7 @@ static TG_PAGED_BUFFER *tg_req_map_kerne
goto err;
}
- addr = pci_map_page(pdev, page, 0, PAGE_SIZE, DMA_BIDIRECTIONAL);
+ addr = dma_map_page(&pdev->dev, page, 0, PAGE_SIZE, DMA_BIDIRECTIONAL);
if (!addr) {
DPRINTK("[2] va:%p can't map\n", buffer);
goto err;
@@ -189,7 +189,7 @@ static TG_PAGED_BUFFER *tg_req_map_kerne
err:
for (; i > 0; i--, pfn--)
- pci_unmap_page(pdev, *pfn << PAGE_SHIFT, PAGE_SIZE, DMA_BIDIRECTIONAL);
+ dma_unmap_page(&pdev->dev, *pfn << PAGE_SHIFT, PAGE_SIZE, DMA_BIDIRECTIONAL);
return ERR_PTR(-ENOMEM);
}
@@ -203,7 +203,7 @@ static inline int tg_req_unmap_internal(
dst->RequestSize + ~PAGE_MASK) >> PAGE_SHIFT;
for (i = 0; i < count; i++)
- pci_unmap_page(req->dev->pci_dev, dst->RequestPages[i] << PAGE_SHIFT, PAGE_SIZE, DMA_BIDIRECTIONAL);
+ dma_unmap_page(&req->dev->pci_dev->dev, dst->RequestPages[i] << PAGE_SHIFT, PAGE_SIZE, DMA_BIDIRECTIONAL);
return count;
}
@@ -264,7 +264,7 @@ static void tg_req_unmap_pages(struct TG
pfn = (u64 *)(dbuf + 1);
for (; npages > 0; npages--, pfn++)
- pci_unmap_page(pdev, (*pfn) << PAGE_SHIFT, PAGE_SIZE, DMA_BIDIRECTIONAL);
+ dma_unmap_page(&pdev->dev, (*pfn) << PAGE_SHIFT, PAGE_SIZE, DMA_BIDIRECTIONAL);
dbuf = (TG_PAGED_BUFFER *)pfn;
}
@@ -374,7 +374,7 @@ static int tg_req_submit(struct TG_PENDI
* also no any offset inside page needed.
*/
req->pg = vmalloc_to_page(dst);
- req->phys = pci_map_page(dev->pci_dev, vmalloc_to_page(dst), 0, PAGE_SIZE, DMA_BIDIRECTIONAL);
+ req->phys = dma_map_page(&dev->pci_dev->dev, vmalloc_to_page(dst), 0, PAGE_SIZE, DMA_BIDIRECTIONAL);
if (!req->phys) {
DPRINTK("Can not allocate memory for DMA mapping\n");
goto out;
@@ -405,7 +405,7 @@ static int tg_req_submit(struct TG_PENDI
out:
if (ret != TG_STATUS_PENDING) {
page_cache_release(req->pg);
- pci_unmap_page(dev->pci_dev, req->phys, PAGE_SIZE, DMA_BIDIRECTIONAL);
+ dma_unmap_page(&dev->pci_dev->dev, req->phys, PAGE_SIZE, DMA_BIDIRECTIONAL);
}
DPRINTK("EXIT\n");
@@ -460,7 +460,7 @@ out_wait:
wait_for_completion(&req->waiting);
out:
page_cache_release(req->pg);
- pci_unmap_page(dev->pci_dev, req->phys, PAGE_SIZE, DMA_BIDIRECTIONAL);
+ dma_unmap_page(&dev->pci_dev->dev, req->phys, PAGE_SIZE, DMA_BIDIRECTIONAL);
DPRINTK("EXIT\n");
return ret;
}
diff -puNr prl-tools-build/kmods/prl_fs/SharedFolders/Guest/Linux/prl_fs/inode.c prl-tools-build/kmods/prl_fs/SharedFolders/Guest/Linux/prl_fs/inode.c
--- prl-tools-build/kmods/prl_fs/SharedFolders/Guest/Linux/prl_fs/inode.c
+++ prl-tools-build/kmods/prl_fs/SharedFolders/Guest/Linux/prl_fs/inode.c
@@ -16,6 +16,7 @@
#include <linux/pagemap.h>
#include <linux/namei.h>
#include <linux/cred.h>
+#include <linux/writeback.h>
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 40)) && \
(LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0))
@@ -57,7 +58,7 @@ unsigned long *prlfs_dfl( struct dentry
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
-#define prl_uaccess_kernel() uaccess_kernel()
+#define prl_uaccess_kernel() (false)
#else
#define prl_uaccess_kernel() segment_eq(get_fs(), KERNEL_DS)
#endif
@@ -954,7 +955,7 @@ static const struct address_space_operat
.writepage = prlfs_writepage,
.write_begin = simple_write_begin,
.write_end = prlfs_write_end,
- .set_page_dirty = __set_page_dirty_nobuffers,
+ .dirty_folio = filemap_dirty_folio,
};

View file

@ -431,8 +431,7 @@ in {
phc-intel = if lib.versionAtLeast kernel.version "4.10" then callPackage ../os-specific/linux/phc-intel { } else null;
# Disable for kernels 4.15 and above due to compatibility issues
prl-tools = if lib.versionOlder kernel.version "4.15" then callPackage ../os-specific/linux/prl-tools { } else null;
prl-tools = callPackage ../os-specific/linux/prl-tools { };
sch_cake = callPackage ../os-specific/linux/sch_cake { };