1{ pkgs, ... }:
2{
3 name = "mtp";
4 meta = with pkgs.lib.maintainers; {
5 maintainers = [
6 matthewcroughan
7 nixinator
8 ];
9 };
10
11 nodes = {
12 client =
13 { config, pkgs, ... }:
14 {
15 # DBUS runs only once a user session is created, which means a user has to
16 # login. Here, we log in as root. Once logged in, the gvfs-daemon service runs
17 # as UID 0 in User-0.service
18 services.getty.autologinUser = "root";
19
20 # XDG_RUNTIME_DIR is needed for running systemd-user services such as
21 # gvfs-daemon as root.
22 environment.variables.XDG_RUNTIME_DIR = "/run/user/0";
23
24 environment.systemPackages = with pkgs; [
25 usbutils
26 glib
27 jmtpfs
28 tree
29 ];
30 services.gvfs.enable = true;
31
32 # Creates a usb-mtp device inside the VM, which is mapped to the host's
33 # /tmp folder, it is able to write files to this location, but only has
34 # permissions to read its own creations.
35 virtualisation.qemu.options = [
36 "-usb"
37 "-device usb-mtp,rootdir=/tmp,readonly=false"
38 ];
39 };
40 };
41
42 testScript =
43 { nodes, ... }:
44 let
45 # Creates a list of QEMU MTP devices matching USB ID (46f4:0004). This
46 # value can be sourced in a shell script. This is so we can loop over the
47 # devices we find, as this test may want to use more than one MTP device
48 # in future.
49 mtpDevices = pkgs.writeScript "mtpDevices.sh" ''
50 export mtpDevices=$(lsusb -d 46f4:0004 | awk {'print $2","$4'} | sed 's/[:-]/ /g')
51 '';
52 # Qemu is only capable of creating an MTP device with Picture Transfer
53 # Protocol. This means that gvfs must use gphoto2:// rather than mtp://
54 # when mounting.
55 # https://github.com/qemu/qemu/blob/970bc16f60937bcfd334f14c614bd4407c247961/hw/usb/dev-mtp.c#L278
56 gvfs = rec {
57 mountAllMtpDevices = pkgs.writeScript "mountAllMtpDevices.sh" ''
58 set -e
59 source ${mtpDevices}
60 for i in $mtpDevices
61 do
62 gio mount "gphoto2://[usb:$i]/"
63 done
64 '';
65 unmountAllMtpDevices = pkgs.writeScript "unmountAllMtpDevices.sh" ''
66 set -e
67 source ${mtpDevices}
68 for i in $mtpDevices
69 do
70 gio mount -u "gphoto2://[usb:$i]/"
71 done
72 '';
73 # gvfsTest:
74 # 1. Creates a 10M test file
75 # 2. Copies it to the device using GIO tools
76 # 3. Checks for corruption with `diff`
77 # 4. Removes the file, then unmounts the disks.
78 gvfsTest = pkgs.writeScript "gvfsTest.sh" ''
79 set -e
80 source ${mtpDevices}
81 ${mountAllMtpDevices}
82 dd if=/dev/urandom of=testFile10M bs=1M count=10
83 for i in $mtpDevices
84 do
85 gio copy ./testFile10M gphoto2://[usb:$i]/
86 ls -lah /run/user/0/gvfs/*/testFile10M
87 gio remove gphoto2://[usb:$i]/testFile10M
88 done
89 ${unmountAllMtpDevices}
90 '';
91 };
92 jmtpfs = {
93 # jmtpfsTest:
94 # 1. Mounts the device on a dir named `phone` using jmtpfs
95 # 2. Puts the current Nixpkgs libmtp version into a file
96 # 3. Checks for corruption with `diff`
97 # 4. Prints the directory tree
98 jmtpfsTest = pkgs.writeScript "jmtpfsTest.sh" ''
99 set -e
100 mkdir phone
101 jmtpfs phone
102 echo "${pkgs.libmtp.version}" > phone/tmp/testFile
103 echo "${pkgs.libmtp.version}" > testFile
104 diff phone/tmp/testFile testFile
105 tree phone
106 '';
107 };
108 in
109 # Using >&2 allows the results of the scripts to be printed to the terminal
110 # when building this test with Nix. Scripts would otherwise complete
111 # silently.
112 ''
113 start_all()
114 client.wait_for_unit("multi-user.target")
115 client.wait_for_unit("dbus.service")
116 client.succeed("${gvfs.gvfsTest} >&2")
117 client.succeed("${jmtpfs.jmtpfsTest} >&2")
118 '';
119}