diff --git a/README.md b/README.md index 799164b..ee0d6f4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,65 @@ +# Rosetta Support for Linux guests + +This fork adds a commandline option `-g ` to specify the name of the directory share tag +used to expose the Rosetta virtiofs directory share to the arm64 Linux guest. The tag can be any name you choose so long +as it conforms to virtiofs naming conventions. + +The virtiofs directory share contains a single linux/arm64 executable named `rosetta`. This executable is an amd64 +emulator which uses the native Rosetta functionality on MacOS to execute amd64 Linux binaries on the arm64 host +running Apple Silicon. + +To actually make use of it in your arm64 Linux guest you must explicitly register a custom binfmt handler in the arm64 +Linux guest by following the installation steps below. + +Example usage of `vftool` with `-g ROSETTA`: +```shell +vftool/build/vftool -k vmlinux -i initrd -g ROSETTA -d root.img -d varlibdocker.img -m 8192 -p 6 -t 0 -a "root=/dev/vda" "$@" +``` + +## PREREQUISITES +The `binfmt-support` Ubuntu package must be installed in the arm64 Linux guest; this package contains the required +`update-binfmts` tool. +```shell +% sudo apt install binfmt-support +``` + +## INSTALLATION +Run these commands in your arm64 Linux guest on every boot: +```shell +% mkdir /tmp/rosetta +% sudo mount -t virtiofs ROSETTA /tmp/rosetta +% sudo /usr/sbin/update-binfmts --install rosetta /tmp/rosetta/rosetta \ + --magic "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00" \ + --mask "\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff" \ + --credentials yes --preserve no --fix-binary yes +``` + +The token `ROSETTA` in the `mount` command above MUST MATCH the tag name given to vftool `-g` commandline option. + +You may see this error on first use of `update-binfmts`; ignore it and repeat the command and it should succeed. +``` +update-binfmts: warning: unable to close /proc/sys/fs/binfmt_misc/register: No such file or directory +update-binfmts: warning: unable to enable binary format rosetta +update-binfmts: exiting due to previous errors +``` + +If you intend to use `docker` in the arm64 Linux guest to run containers with the `linux/amd64` platform, it's a good +idea to set this environment variable in your MacOS host for the MacOS `docker` client to use as a default: + +```shell +export DOCKER_DEFAULT_PLATFORM=linux/amd64 +``` + +This environment variable could also be specified in the `/lib/systemd/system/docker.service` systemd unit file on the +arm64 Linux guest by adding this line to the `[Service]` stanza: + +```ini +Environment=DOCKER_DEFAULT_PLATFORM=linux/amd64 +``` + +## Original README follows + +--- # Virtualization.framework tool (vftool) Here lies a _really minimalist_ and very noddy command-line wrapper to run VMs in the macOS Big Sur Virtualization.framework. @@ -38,6 +100,7 @@ The following command-line arguments are supported: -i -d -c + -s [:[:{ro, rw}]] -b -p -m @@ -50,6 +113,14 @@ The `-t` option permits the console to either use stdin/stdout (option `0`), or Multiple disc images can be attached by using several `-d` or `-c` options. The discs are attached in the order they are given on the command line, which should then influence which device they appear as. For example, `-d foo -d bar -c blah` will create three virtio-blk devices, `/dev/vda`, `/dev/vdb`, `/dev/vdc` attached to _foo_, _bar_ and _blah_ respectively. Up to 8 discs can be attached. +Up to 8 shared directories may be attached with the `-s` option. They are each labelled with a tag; +if none is specified, the directory name is used. In they guest VM, they may be mounted using: +``` +mount -t virtiofs +``` +Note that this requires virtiofs support, which is available in recent distributions such as Ubuntu +22.04. Directories may be specified as read only by adding the `:ro` suffix. + The kernel should be uncompressed. The initrd may be a gz. Disc images are raw/flat files (nothing fancy like qcow2). When starting vftool, you will see output similar to: diff --git a/vftool/main.m b/vftool/main.m index 2e1f528..9117e65 100644 --- a/vftool/main.m +++ b/vftool/main.m @@ -24,12 +24,19 @@ #define VERSION "v0.3 10/12/2020" #define MAX_DISCS 8 - +#define MAX_SHARES 8 + struct disc_info { NSString *path; bool readOnly; }; +struct share_info { + NSString *path; // Path on host system + NSString *tag; // Tag on guest system + bool readOnly; +}; + /* ******************************************************************** */ /* PTY management*/ @@ -96,7 +103,10 @@ static int createPty(bool waitForConnection) NSString *initrd_path, struct disc_info *dinfo, unsigned int num_discs, - NSString *bridged_eth) + struct share_info *sinfo, + unsigned int num_shares, + NSString *bridged_eth, + NSString *rosetta_tag) { /* **************************************************************** */ /* Linux bootloader setup: @@ -210,6 +220,51 @@ static int createPty(bool waitForConnection) [conf setStorageDevices:discs]; + // Shared directories + NSArray *shares = @[]; + + for (unsigned int i = 0; i < num_shares; i++) { + NSLog(@"+++ Attaching shared directory '%@' with tag '%@' with readOnly=%@ \n", + sinfo[i].path, sinfo[i].tag, sinfo[i].readOnly ? @"YES" : @"NO"); + NSURL *shareURL = [NSURL fileURLWithPath:sinfo[i].path]; + VZSharedDirectory *sharedDirectory = [[VZSharedDirectory alloc] + initWithURL: shareURL + readOnly: sinfo[i].readOnly]; + + VZSingleDirectoryShare *singleDirectoryShare = [[VZSingleDirectoryShare alloc] + initWithDirectory: sharedDirectory]; + + VZVirtioFileSystemDeviceConfiguration *share_conf = [[VZVirtioFileSystemDeviceConfiguration alloc] + initWithTag: sinfo[i].tag]; + share_conf.share = singleDirectoryShare; + shares = [shares arrayByAddingObject:share_conf]; + } + [conf setDirectorySharingDevices:shares]; + + // expose the Rosetta directory share: + NSString *tag = @"ROSETTA"; + + // expose the Rosetta directory share as value of rosetta_tag NSString: + if (rosetta_tag) { + NSError *validationError; + if (![VZVirtioFileSystemDeviceConfiguration validateTag:rosetta_tag error:&validationError]) { + // Handle validation error here. + NSLog(@"-- Configuration validation failure! %@\n", validationError); + return nil; + } + + VZLinuxRosettaDirectoryShare *rosettaDirectoryShare = [[VZLinuxRosettaDirectoryShare alloc] initWithError:&validationError]; + if (validationError) { + NSLog(@"-- Configuration validation failure! %@\n", validationError); + return nil; + } + + VZVirtioFileSystemDeviceConfiguration *fileSystemDevice = [[VZVirtioFileSystemDeviceConfiguration alloc] initWithTag:rosetta_tag]; + fileSystemDevice.share = rosettaDirectoryShare; + + conf.directorySharingDevices = @[fileSystemDevice]; + } + return conf; } @@ -219,17 +274,20 @@ static void usage(const char *me) fprintf(stderr, "vftool version " VERSION "\n\n" "Syntax:\n\t%s \n\n" "Options:\n" - "\t-k [REQUIRED]\n" + "\t-k [REQUIRED]\n" "\t-a \n" "\t-i \n" "\t-d \n" - "\t-c (As -d, but read-only)\n" - "\t-b (Default NAT)\n" - "\t-p (Default 1)\n" - "\t-m (Default 512MB)\n" - "\t-t (0 = stdio, 1 = pty (default))\n" + "\t-c (As -d, but read-only)\n" + "\t-s [:[:{ro, rw}]] (default tag: dir name,\n" + "\t default mode: read/write, at most %d shares) \n" + "\t-b (Default NAT)\n" + "\t-p (Default 1)\n" + "\t-m (Default 512MB)\n" + "\t-t (0 = stdio, 1 = pty (default))\n" + "\t-g \n" "\n\tSpecify multiple discs with multiple -d/-c options, in order (max %d)\n", - me, MAX_DISCS); + me, MAX_SHARES, MAX_DISCS); } @@ -242,6 +300,7 @@ int main(int argc, char *argv[]) NSString *disc_path = NULL; NSString *cdrom_path = NULL; NSString *eth_if = NULL; + NSString *rosetta_tag = NULL; unsigned int cpus = 0; unsigned int mem = 0; unsigned int tty_type = 1; @@ -249,8 +308,11 @@ int main(int argc, char *argv[]) struct disc_info dinfo[MAX_DISCS]; unsigned int num_discs = 0; + struct share_info sinfo[MAX_SHARES]; + unsigned int num_shares = 0; + int ch; - while ((ch = getopt(argc, argv, "k:a:i:d:c:b:p:m:t:h")) != -1) { + while ((ch = getopt(argc, argv, "k:a:i:d:c:s:b:p:m:t:g:h")) != -1) { switch (ch) { case 'k': kern_path = [NSString stringWithUTF8String:optarg]; @@ -272,6 +334,40 @@ int main(int argc, char *argv[]) dinfo[num_discs].readOnly = (ch == 'c'); num_discs++; break; + case 's': + if (num_shares > MAX_SHARES-1) { + usage(argv[0]); + fprintf(stderr, "\nError: Too many shared directories specified (max %d)\n\n", MAX_SHARES); + return 1; + } + + NSString *share_string = [NSString stringWithUTF8String:optarg]; + NSArray *share_components = [share_string componentsSeparatedByString:@":"]; + + sinfo[num_shares].path = share_components[0]; + sinfo[num_shares].tag = [share_components[0] lastPathComponent]; + sinfo[num_shares].readOnly = false; + + if ([share_components count] > 1){ + sinfo[num_shares].tag = share_components[1]; + } + if ([share_components count] > 2){ + if ([share_components[2] isEqualToString:@"ro"]){ + sinfo[num_shares].readOnly = true; + } else if (![share_components[2] isEqualToString:@"rw"]){ + usage(argv[0]); + fprintf(stderr, "\nError: Third part of share argument must be one of 'rw' and 'ro', but '%s' was given.\n\n", [share_components[2] UTF8String]); + return 1; + } + } + if ([share_components count] > 3){ + usage(argv[0]); + fprintf(stderr, "\nError: Share argument should consist of at most three components separated by ':', but %s was given.\n\n", [share_string UTF8String]); + return 1; + } + num_shares++; + break; + case 'b': eth_if = [NSString stringWithUTF8String:optarg]; break; @@ -289,6 +385,9 @@ int main(int argc, char *argv[]) return 1; } break; + case 'g': + rosetta_tag = [NSString stringWithUTF8String:optarg]; + break; case 'h': default: @@ -323,8 +422,10 @@ int main(int argc, char *argv[]) VZVirtualMachineConfiguration *conf = getVMConfig(mem, cpus, tty_type, cmdline, kern_path, initrd_path, dinfo, num_discs, - eth_if); - + sinfo, num_shares, + eth_if, + rosetta_tag); + if (!conf) { NSLog(@"Couldn't create configuration for VM.\n"); return 1;