From c91006e6e85cc0f24ecd6bb79b610d85b1f35255 Mon Sep 17 00:00:00 2001 From: David Sheets Date: Tue, 22 Dec 2015 18:00:05 +0000 Subject: [PATCH] Add 9pudfuse and fuserun.sh for testing remote FUSE file systems --- alpine/Dockerfile | 5 +- alpine/packages/9pinit/etc/init.d/9pinit | 4 +- alpine/packages/9pudfuse/.gitignore | 1 + alpine/packages/9pudfuse/.merlin | 6 + alpine/packages/9pudfuse/Dockerfile | 17 +++ alpine/packages/9pudfuse/Makefile | 10 ++ alpine/packages/9pudfuse/fuse_socket_9p.ml | 163 +++++++++++++++++++++ alpine/packages/Makefile | 2 + xhyve/fuserun.sh | 26 ++++ 9 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 alpine/packages/9pudfuse/.gitignore create mode 100644 alpine/packages/9pudfuse/.merlin create mode 100644 alpine/packages/9pudfuse/Dockerfile create mode 100644 alpine/packages/9pudfuse/Makefile create mode 100644 alpine/packages/9pudfuse/fuse_socket_9p.ml create mode 100755 xhyve/fuserun.sh diff --git a/alpine/Dockerfile b/alpine/Dockerfile index 022c9035e..f77fea12f 100644 --- a/alpine/Dockerfile +++ b/alpine/Dockerfile @@ -15,7 +15,7 @@ RUN \ sfdisk lvm2 syslinux \ openrc busybox-initscripts \ alpine-conf \ - strace + strace fuse COPY etc /etc/ @@ -24,6 +24,8 @@ COPY mkinitrd.sh /bin/ COPY packages/9pudc/9pudc /sbin/ COPY packages/9pudc/etc /etc/ +COPY packages/9pudfuse/9pudfuse /sbin/ +COPY packages/9pudfuse/etc /etc/ COPY packages/mdnstool/mdnstool /sbin/ COPY packages/mdnstool/etc /etc/ COPY packages/docker/docker /usr/bin/ @@ -62,6 +64,7 @@ RUN \ rc-update add docker default && \ rc-update add 9pinit default && \ rc-update add 9pudc default && \ + rc-update add 9pudfuse default && \ rc-update add mdnstool default && \ rc-update add automount boot && \ rc-update add diagnostics default && \ diff --git a/alpine/packages/9pinit/etc/init.d/9pinit b/alpine/packages/9pinit/etc/init.d/9pinit index f87f75a48..e2710ebf8 100755 --- a/alpine/packages/9pinit/etc/init.d/9pinit +++ b/alpine/packages/9pinit/etc/init.d/9pinit @@ -7,5 +7,7 @@ start() mount -t 9p -o trans=virtio,dfltuid=1001,dfltgid=50,version=9p2000 socket /Socket mkdir -p /Mac mount -t 9p -o trans=virtio,dfltuid=1001,dfltgid=50,version=9p2000.u plan9 /Mac - eend 0 + mkdir -p /Transfuse + mount -t 9p -o trans=virtio,dfltuid=1001,dfltgid=50,version=9p2000 fuse /Transfuse + eend 0 } diff --git a/alpine/packages/9pudfuse/.gitignore b/alpine/packages/9pudfuse/.gitignore new file mode 100644 index 000000000..1f9173621 --- /dev/null +++ b/alpine/packages/9pudfuse/.gitignore @@ -0,0 +1 @@ +9pudfuse diff --git a/alpine/packages/9pudfuse/.merlin b/alpine/packages/9pudfuse/.merlin new file mode 100644 index 000000000..bde497c73 --- /dev/null +++ b/alpine/packages/9pudfuse/.merlin @@ -0,0 +1,6 @@ +PKG result +PKG stringext +PKG fd-send-recv + +S . +B . diff --git a/alpine/packages/9pudfuse/Dockerfile b/alpine/packages/9pudfuse/Dockerfile new file mode 100644 index 000000000..9629de4f2 --- /dev/null +++ b/alpine/packages/9pudfuse/Dockerfile @@ -0,0 +1,17 @@ +FROM justincormack/alpine-pkgsrc-ocaml:latest + +ENV \ + OPAMYES=true \ + CAML_LD_LIBRARY_PATH="/root/.opam/4.02.3/lib/stublibs:/usr/pkg/lib/ocaml/stublibs" \ + MANPATH="/root/.opam/4.02.3/man:" \ + PERL5LIB="/root/.opam/4.02.3/lib/perl5" \ + OCAML_TOPLEVEL_PATH="/root/.opam/4.02.3/lib/toplevel" \ + PATH=/root/.opam/4.02.3/bin:$PATH + +RUN mkdir -p /ocaml/src/9pudfuse +WORKDIR /ocaml/src/9pudfuse + +COPY . /ocaml/src/9pudfuse +RUN opam install result stringext fd-send-recv +RUN ocamlbuild -tag debug -use-ocamlfind -package stringext,fd-send-recv,result fuse_socket_9p.native + diff --git a/alpine/packages/9pudfuse/Makefile b/alpine/packages/9pudfuse/Makefile new file mode 100644 index 000000000..b75d5be35 --- /dev/null +++ b/alpine/packages/9pudfuse/Makefile @@ -0,0 +1,10 @@ +all: 9pudfuse + +9pudfuse: Dockerfile fuse_socket_9p.ml + docker build -t 9pudfuse:test . + docker run 9pudfuse:test \ + cat /ocaml/src/9pudfuse/fuse_socket_9p.native > 9pudfuse + chmod 755 9pudfuse + +clean: + rm -f 9pudfuse diff --git a/alpine/packages/9pudfuse/fuse_socket_9p.ml b/alpine/packages/9pudfuse/fuse_socket_9p.ml new file mode 100644 index 000000000..bfda8a47a --- /dev/null +++ b/alpine/packages/9pudfuse/fuse_socket_9p.ml @@ -0,0 +1,163 @@ + +exception ReadClosed + +let fusermount = "fusermount" +let path = "/Transfuse" + +module Log = struct + let fatal fmt = + Printf.ksprintf (fun s -> prerr_endline s; exit 1) fmt + let error fmt = + Printf.ksprintf (fun s -> prerr_endline s) fmt + let info fmt = + Printf.ksprintf (fun s -> prerr_endline s) fmt +end + +let events_path = path ^ "/events" + +let try_fork caller_name = + try + Unix.fork () + with Unix.Unix_error (err,"fork",_) -> + Log.fatal "%s fork failed: %s" caller_name (Unix.error_message err) + +let check_status = function + | Unix.WEXITED 0 -> Result.Ok () + | Unix.WEXITED k -> + Result.Error ("exit code "^(string_of_int k)) + | Unix.WSIGNALED k -> + Result.Error ("ocaml kill signal "^(string_of_int k)) + | Unix.WSTOPPED k -> + Result.Error ("ocaml stop signal "^(string_of_int k)) + +let finally f at_end = + let r = try f () with e -> (at_end (); raise e) in + at_end (); + r + +let copy description dst src = + let sz = 1 lsl 16 in + let buf = Bytes.create sz in + let pnum = ref 0 in + let rec loop () = + let n = Unix.read src buf 0 sz in + (if n = 0 then raise ReadClosed); + + let fd = Unix.( + openfile ("/tmp/"^description^"_"^(string_of_int !pnum)) + [O_WRONLY; O_CREAT] 0o600) in + let k = Unix.write fd buf 0 n in + assert (k = n); + Unix.close fd; + incr pnum; + + let written = Unix.write dst buf 0 n in + (if n <> written + then Log.error "copy of %s read %d but wrote %d" description n written); + loop () + in + try loop () + with + | ReadClosed -> raise ReadClosed + | e -> (Log.error "copy for %s failed" description; raise e) + +let with_reader id f = + let read_path = Printf.sprintf "%s/connections/%d/read" path id in + let read_fd = Unix.(openfile read_path [O_RDONLY] 0o000) in + try finally (fun () -> + f read_path read_fd + ) (fun () -> + Unix.close read_fd + ) with + | ReadClosed -> exit 0 + +let with_writer id f = + let write_path = Printf.sprintf "%s/connections/%d/write" path id in + let write_fd = Unix.(openfile write_path [O_WRONLY] 0o000) in + finally (fun () -> + f write_path write_fd + ) (fun () -> + Unix.close write_fd; + Unix.unlink write_path + ) + +let read_opts id = with_reader id (fun read_path read_fd -> + let sz = 512 in + let buf = Bytes.create sz in + let n = Unix.read read_fd buf 0 sz in + let opts = Stringext.split ~on:'\000' (Bytes.sub buf 0 n) in + Array.of_list opts +) + +let get_fuse_sock opts = + let wsock, rsock = Unix.(socketpair PF_UNIX SOCK_STREAM 0) in + let wfd = Fd_send_recv.int_of_fd wsock in + let opts = Array.append [|fusermount|] opts in + let pid = Unix.(create_process_env fusermount opts + [|"_FUSE_COMMFD="^(string_of_int wfd)|] + stdin stdout stderr) in + let _, status = Unix.waitpid [] pid in + let () = Unix.(shutdown wsock SHUTDOWN_ALL) in + match check_status status with + | Result.Error str -> + let opts = String.concat " " (Array.to_list opts) in + Log.fatal "%s: %s" opts str + | Result.Ok () -> + (* We must read at least 1 byte, by POSIX! *) + let _, _, fd = Fd_send_recv.recv_fd rsock "\000" 0 1 [] in + let () = Unix.(shutdown rsock SHUTDOWN_ALL) in + fd + +(* readers fork into a new process *) +let start_reader id fuse = + match try_fork "start_reader" with + | 0 -> (* child *) + with_reader id (fun _read_path read_fd -> + copy ("reader_"^string_of_int id) fuse read_fd + ) + | _child_pid -> (* parent *) + () + +(* writers stay in the calling process *) +let start_writer id fuse = with_writer id (fun write_path write_fd -> + copy ("writer_"^string_of_int id) write_fd fuse +) + +let handle_connection id = + Log.info "handle_connection %d" id; + match try_fork "handle_connection" with + | 0 -> (* child *) + let opts = read_opts id in + let fuse = get_fuse_sock opts in + start_reader id fuse; + begin try ignore (start_writer id fuse); exit 0 + with + | e -> prerr_endline (Printexc.to_string e); exit 1 + end + | _child_pid -> (* parent *) + () + +let connection_loop () = + let events = Unix.(openfile events_path [O_RDONLY] 0o000) in + (* 512 bytes is easily big enough to read a whole connection id *) + let sz = 512 in + let buf = Bytes.create sz in + let rec recv () = + begin try + let n = Unix.read events buf 0 sz in + let s = String.trim Bytes.(to_string (sub buf 0 n)) in + let id = int_of_string s in + handle_connection id; + with + | Unix.Unix_error (err,"read",path) -> + Log.fatal "Error reading events file %s: %s" + path (Unix.error_message err) + | Failure "int_of_string" -> + Log.fatal "Failed to parse integer connection id" + end; + recv () + in + recv () + +;; +connection_loop () diff --git a/alpine/packages/Makefile b/alpine/packages/Makefile index 28c9ee6b2..bd271e67c 100644 --- a/alpine/packages/Makefile +++ b/alpine/packages/Makefile @@ -1,9 +1,11 @@ all: $(MAKE) -C 9pudc + $(MAKE) -C 9pudfuse $(MAKE) -C mdnstool $(MAKE) -C docker clean: $(MAKE) -C 9pudc clean + $(MAKE) -C 9pudfuse clean $(MAKE) -C mdnstool clean $(MAKE) -C docker clean diff --git a/xhyve/fuserun.sh b/xhyve/fuserun.sh new file mode 100755 index 000000000..35d54bcee --- /dev/null +++ b/xhyve/fuserun.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +KERNEL="../kernel/vmlinuz64" +INITRD="../alpine/initrd.img" +CMDLINE="earlyprintk=serial console=ttyS0 virtio_pci.force_legacy=1" + +MEM="-m 1G" +#SMP="-c 2" +NET="" +if (( $EUID != 0 )); then + printf "Warning: not running as root will have no networking!\n\n" + sleep 1 +else +NET="-s 2:0,virtio-net" +fi +#IMG_CD="-s 3,ahci-cd,/somepath/somefile.iso" +#IMG_HDD="-s 4,virtio-blk,/somepath/somefile.img" +PCI_DEV="-s 0:0,hostbridge -s 31,lpc" +RND="-s 5,virtio-rnd" +FS="-s 6,virtio-9p,path=9pudfuse.sock,tag=fuse" +LPC_DEV="-l com1,stdio" +ACPI="-A" +CLOCK="-u" +#UUID="-U deadbeef-dead-dead-dead-deaddeafbeef" + +build/xhyve $ACPI $MEM $SMP $PCI_DEV $LPC_DEV $NET $IMG_CD $IMG_HDD $RND $FS $UUID $CLOCK -f kexec,$KERNEL,$INITRD,"$CMDLINE"