Browse Source

Merge branch 'view-only'

* view-only:
  Add file managers integration for qvm-open-in-dvm --view-only
  qvm-open-in-vm: mark file as read-only if opened with --view-only
  qvm-open-in-vm: implement --view-only option
  qubes-rpc: fix code style - indent with spaces
Marek Marczykowski-Górecki 6 years ago
parent
commit
bd445742fb

+ 14 - 1
misc/uca_qubes.xml

@@ -59,7 +59,7 @@
 </action>
 <action>
 	<icon>document-open</icon>
-	<name>Open in DisposableVM</name>
+	<name>Edit in DisposableVM</name>
 	<unique-id>1507455559234996-8</unique-id>
 	<command>/usr/lib/qubes/qvm-actions.sh opendvm %F</command>
 	<description></description>
@@ -70,3 +70,16 @@
 	<text-files/>
 	<video-files/>
 </action>
+<action>
+	<icon>document-open</icon>
+	<name>View in DisposableVM</name>
+	<unique-id>1507455559234997-9</unique-id>
+	<command>/usr/lib/qubes/qvm-actions.sh viewdvm %F</command>
+	<description></description>
+	<patterns>*</patterns>
+	<audio-files/>
+	<image-files/>
+	<other-files/>
+	<text-files/>
+	<video-files/>
+</action>

+ 1 - 0
qubes-rpc/dvm2.h

@@ -1,2 +1,3 @@
 #define DVM_FILENAME_SIZE 256
 #define DVM_SPOOL "/home/user/.dvmspool"
+#define DVM_VIEW_ONLY_PREFIX "view-only-"

+ 34 - 34
qubes-rpc/gui-fatal.c

@@ -10,58 +10,58 @@
 
 static void fix_display(void)
 {
-	setenv("DISPLAY", ":0", 1);
+    setenv("DISPLAY", ":0", 1);
 }
 
 static void produce_message(const char * type, const char *fmt, va_list args)
 {
-	char *dialog_msg;
-	char buf[1024];
-	(void) vsnprintf(buf, sizeof(buf), fmt, args);
-	if (asprintf(&dialog_msg, "%s: %s: %s (error type: %s)",
-		 program_invocation_short_name, type, buf, strerror(errno)) < 0) {
-		fprintf(stderr, "Failed to allocate memory for error message :(\n");
-		return;
-	}
-	fprintf(stderr, "%s\n", dialog_msg);
-	switch (fork()) {
-	case -1:
-		exit(1);	//what else
-	case 0:
-		if (geteuid() == 0)
-			if (setuid(getuid()) != 0)
-				perror("setuid failed, calling kdialog/zenity as root");
-		fix_display();
+    char *dialog_msg;
+    char buf[1024];
+    (void) vsnprintf(buf, sizeof(buf), fmt, args);
+    if (asprintf(&dialog_msg, "%s: %s: %s (error type: %s)",
+         program_invocation_short_name, type, buf, strerror(errno)) < 0) {
+        fprintf(stderr, "Failed to allocate memory for error message :(\n");
+        return;
+    }
+    fprintf(stderr, "%s\n", dialog_msg);
+    switch (fork()) {
+    case -1:
+        exit(1);    //what else
+    case 0:
+        if (geteuid() == 0)
+            if (setuid(getuid()) != 0)
+                perror("setuid failed, calling kdialog/zenity as root");
+        fix_display();
 #ifdef USE_KDIALOG
-		execlp("/usr/bin/kdialog", "kdialog", "--sorry", dialog_msg, NULL);
+        execlp("/usr/bin/kdialog", "kdialog", "--sorry", dialog_msg, NULL);
 #else
 
-		execlp("/usr/bin/zenity", "zenity", "--error",  "--text", dialog_msg, NULL);
+        execlp("/usr/bin/zenity", "zenity", "--error",  "--text", dialog_msg, NULL);
 #endif
-		exit(1);
-	default:;
-	}
-	free(dialog_msg);
+        exit(1);
+    default:;
+    }
+    free(dialog_msg);
 }
 
 void gui_fatal(const char *fmt, ...)
 {
-	va_list args;
-	va_start(args, fmt);
-	produce_message("Fatal error", fmt, args);
-	va_end(args);
-	exit(1);
+    va_list args;
+    va_start(args, fmt);
+    produce_message("Fatal error", fmt, args);
+    va_end(args);
+    exit(1);
 }
 
 void qfile_gui_fatal(const char *fmt, va_list args) {
-	produce_message("Fatal error", fmt, args);
+    produce_message("Fatal error", fmt, args);
     exit(1);
 }
 
 void gui_nonfatal(const char *fmt, ...)
 {
-	va_list args;
-	va_start(args, fmt);
-	produce_message("Information", fmt, args);
-	va_end(args);
+    va_list args;
+    va_start(args, fmt);
+    produce_message("Information", fmt, args);
+    va_end(args);
 }

+ 81 - 81
qubes-rpc/qfile-agent.c

@@ -13,107 +13,107 @@
 #include <libqubes-rpc-filecopy.h>
 
 enum {
-	PROGRESS_FLAG_NORMAL,
-	PROGRESS_FLAG_INIT,
-	PROGRESS_FLAG_DONE
+    PROGRESS_FLAG_NORMAL,
+    PROGRESS_FLAG_INIT,
+    PROGRESS_FLAG_DONE
 };
 
 void do_notify_progress(long long total, int flag)
 {
-	const char *du_size_env = getenv("FILECOPY_TOTAL_SIZE");
-	const char *progress_type_env = getenv("PROGRESS_TYPE");
-	const char *saved_stdout_env = getenv("SAVED_FD_1");
-	int ignore;
-	if (!progress_type_env)
-		return;
-	if (!strcmp(progress_type_env, "console") && du_size_env) {
-		char msg[256];
-		snprintf(msg, sizeof(msg), "sent %lld/%lld KB\r",
-			 total / 1024, strtoull(du_size_env, NULL, 0));
-		ignore = write(2, msg, strlen(msg));
-		if (flag == PROGRESS_FLAG_DONE)
-			ignore = write(2, "\n", 1);
-	}
-	if (!strcmp(progress_type_env, "gui") && saved_stdout_env) {
-		char msg[256];
-		snprintf(msg, sizeof(msg), "%lld\n", total);
-		ignore = write(strtoul(saved_stdout_env, NULL, 0), msg,
-				strlen(msg));
-	}
-	if (ignore < 0) {
-		/* silence gcc warning */
-	}
+    const char *du_size_env = getenv("FILECOPY_TOTAL_SIZE");
+    const char *progress_type_env = getenv("PROGRESS_TYPE");
+    const char *saved_stdout_env = getenv("SAVED_FD_1");
+    int ignore;
+    if (!progress_type_env)
+        return;
+    if (!strcmp(progress_type_env, "console") && du_size_env) {
+        char msg[256];
+        snprintf(msg, sizeof(msg), "sent %lld/%lld KB\r",
+             total / 1024, strtoull(du_size_env, NULL, 0));
+        ignore = write(2, msg, strlen(msg));
+        if (flag == PROGRESS_FLAG_DONE)
+            ignore = write(2, "\n", 1);
+    }
+    if (!strcmp(progress_type_env, "gui") && saved_stdout_env) {
+        char msg[256];
+        snprintf(msg, sizeof(msg), "%lld\n", total);
+        ignore = write(strtoul(saved_stdout_env, NULL, 0), msg,
+                strlen(msg));
+    }
+    if (ignore < 0) {
+        /* silence gcc warning */
+    }
 }
 
 void notify_progress(int size, int flag)
 {
-	static long long total = 0;
-	static long long prev_total = 0;
-	total += size;
-	if (total > prev_total + PROGRESS_NOTIFY_DELTA
-	    || (flag != PROGRESS_FLAG_NORMAL)) {
-		// check for possible error from qfile-unpacker; if error occured,
-		// exit() will be called, so don't bother with current state
-		// (notify_progress can be called as callback from copy_file())
-		if (flag == PROGRESS_FLAG_NORMAL)
-			wait_for_result();
-		do_notify_progress(total, flag);
-		prev_total = total;
-	}
+    static long long total = 0;
+    static long long prev_total = 0;
+    total += size;
+    if (total > prev_total + PROGRESS_NOTIFY_DELTA
+        || (flag != PROGRESS_FLAG_NORMAL)) {
+        // check for possible error from qfile-unpacker; if error occured,
+        // exit() will be called, so don't bother with current state
+        // (notify_progress can be called as callback from copy_file())
+        if (flag == PROGRESS_FLAG_NORMAL)
+            wait_for_result();
+        do_notify_progress(total, flag);
+        prev_total = total;
+    }
 }
 
 
 char *get_abs_path(const char *cwd, const char *pathname)
 {
-	char *ret;
-	if (pathname[0] == '/')
-		return strdup(pathname);
-	if (asprintf(&ret, "%s/%s", cwd, pathname) < 0)
-		return NULL;
-	else
-		return ret;
+    char *ret;
+    if (pathname[0] == '/')
+        return strdup(pathname);
+    if (asprintf(&ret, "%s/%s", cwd, pathname) < 0)
+        return NULL;
+    else
+        return ret;
 }
 
 int main(int argc, char **argv)
 {
-	int i;
-	char *entry;
-	char *cwd;
-	char *sep;
-	int ignore_symlinks = 0;
+    int i;
+    char *entry;
+    char *cwd;
+    char *sep;
+    int ignore_symlinks = 0;
 
-	qfile_pack_init();
-	register_error_handler(qfile_gui_fatal);
-	register_notify_progress(&notify_progress);
-	notify_progress(0, PROGRESS_FLAG_INIT);
-	cwd = getcwd(NULL, 0);
-	for (i = 1; i < argc; i++) {
-		if (strcmp(argv[i], "--ignore-symlinks")==0) {
-			ignore_symlinks = 1;
-			continue;
-		}
+    qfile_pack_init();
+    register_error_handler(qfile_gui_fatal);
+    register_notify_progress(&notify_progress);
+    notify_progress(0, PROGRESS_FLAG_INIT);
+    cwd = getcwd(NULL, 0);
+    for (i = 1; i < argc; i++) {
+        if (strcmp(argv[i], "--ignore-symlinks")==0) {
+            ignore_symlinks = 1;
+            continue;
+        }
 
-		entry = get_abs_path(cwd, argv[i]);
+        entry = get_abs_path(cwd, argv[i]);
 
-		do {
-			sep = rindex(entry, '/');
-			if (!sep)
-				gui_fatal
-				    ("Internal error: nonabsolute filenames not allowed");
-			*sep = 0;
-		} while (sep[1] == 0);
-		if (entry[0] == 0) {
-			if (chdir("/") < 0) {
-				gui_fatal("Internal error: chdir(\"/\") failed?!");
-			}
-		} else if (chdir(entry))
-			gui_fatal("chdir to %s", entry);
-		do_fs_walk(sep + 1, ignore_symlinks);
-		free(entry);
-	}
-	notify_end_and_wait_for_result();
-	notify_progress(0, PROGRESS_FLAG_DONE);
-	return 0;
+        do {
+            sep = rindex(entry, '/');
+            if (!sep)
+                gui_fatal
+                    ("Internal error: nonabsolute filenames not allowed");
+            *sep = 0;
+        } while (sep[1] == 0);
+        if (entry[0] == 0) {
+            if (chdir("/") < 0) {
+                gui_fatal("Internal error: chdir(\"/\") failed?!");
+            }
+        } else if (chdir(entry))
+            gui_fatal("chdir to %s", entry);
+        do_fs_walk(sep + 1, ignore_symlinks);
+        free(entry);
+    }
+    notify_end_and_wait_for_result();
+    notify_progress(0, PROGRESS_FLAG_DONE);
+    return 0;
 }
 
 

+ 68 - 68
qubes-rpc/qfile-unpacker.c

@@ -17,81 +17,81 @@
 #define INCOMING_DIR_ROOT "/home/user/QubesIncoming"
 int prepare_creds_return_uid(const char *username)
 {
-	const struct passwd *pwd;
-	pwd = getpwnam(username);
-	if (!pwd) {
-		perror("getpwnam");
-		exit(1);
-	}
-	setenv("HOME", pwd->pw_dir, 1);
-	setenv("USER", username, 1);
-	if (setgid(pwd->pw_gid) < 0)
-		gui_fatal("Error setting group permissions");
-	if (initgroups(username, pwd->pw_gid) < 0)
-		gui_fatal("Error initializing groups");
-	if (setfsuid(pwd->pw_uid) < 0)
-		gui_fatal("Error setting filesystem level permissions");
-	return pwd->pw_uid;
+    const struct passwd *pwd;
+    pwd = getpwnam(username);
+    if (!pwd) {
+        perror("getpwnam");
+        exit(1);
+    }
+    setenv("HOME", pwd->pw_dir, 1);
+    setenv("USER", username, 1);
+    if (setgid(pwd->pw_gid) < 0)
+        gui_fatal("Error setting group permissions");
+    if (initgroups(username, pwd->pw_gid) < 0)
+        gui_fatal("Error initializing groups");
+    if (setfsuid(pwd->pw_uid) < 0)
+        gui_fatal("Error setting filesystem level permissions");
+    return pwd->pw_uid;
 }
 
 int main(int argc __attribute((__unused__)), char ** argv __attribute__((__unused__)))
 {
-	char *incoming_dir;
-	int uid, ret;
-	pid_t pid;
-	const char *remote_domain;
-	char *procdir_path;
-	int procfs_fd;
+    char *incoming_dir;
+    int uid, ret;
+    pid_t pid;
+    const char *remote_domain;
+    char *procdir_path;
+    int procfs_fd;
 
-	uid = prepare_creds_return_uid("user");
+    uid = prepare_creds_return_uid("user");
 
-	remote_domain = getenv("QREXEC_REMOTE_DOMAIN");
-	if (!remote_domain) {
-		gui_fatal("Cannot get remote domain name");
-		exit(1);
-	}
-	mkdir(INCOMING_DIR_ROOT, 0700);
-	if (asprintf(&incoming_dir, "%s/%s", INCOMING_DIR_ROOT, remote_domain) < 0)
-		gui_fatal("Error allocating memory");
-	mkdir(incoming_dir, 0700);
-	if (chdir(incoming_dir))
-		gui_fatal("Error chdir to %s", incoming_dir);
+    remote_domain = getenv("QREXEC_REMOTE_DOMAIN");
+    if (!remote_domain) {
+        gui_fatal("Cannot get remote domain name");
+        exit(1);
+    }
+    mkdir(INCOMING_DIR_ROOT, 0700);
+    if (asprintf(&incoming_dir, "%s/%s", INCOMING_DIR_ROOT, remote_domain) < 0)
+        gui_fatal("Error allocating memory");
+    mkdir(incoming_dir, 0700);
+    if (chdir(incoming_dir))
+        gui_fatal("Error chdir to %s", incoming_dir);
 
-	if (mount(".", ".", NULL, MS_BIND | MS_NODEV | MS_NOEXEC | MS_NOSUID, NULL) < 0)
-		gui_fatal("Failed to mount a directory %s", incoming_dir);
+    if (mount(".", ".", NULL, MS_BIND | MS_NODEV | MS_NOEXEC | MS_NOSUID, NULL) < 0)
+        gui_fatal("Failed to mount a directory %s", incoming_dir);
 
-	/* parse the input in unprivileged child process, parent will hold root
-	 * access to unmount incoming dir */
-	switch (pid=fork()) {
-		case -1:
-			gui_fatal("Failed to create new process");
-		case 0:
-			if (asprintf(&procdir_path, "/proc/%d/fd", getpid()) < 0) {
-				gui_fatal("Error allocating memory");
-			}
-			procfs_fd = open(procdir_path, O_DIRECTORY | O_RDONLY);
-			if (procfs_fd < 0)
-				perror("Failed to open /proc");
-			else
-				set_procfs_fd(procfs_fd);
-			free(procdir_path);
+    /* parse the input in unprivileged child process, parent will hold root
+     * access to unmount incoming dir */
+    switch (pid=fork()) {
+        case -1:
+            gui_fatal("Failed to create new process");
+        case 0:
+            if (asprintf(&procdir_path, "/proc/%d/fd", getpid()) < 0) {
+                gui_fatal("Error allocating memory");
+            }
+            procfs_fd = open(procdir_path, O_DIRECTORY | O_RDONLY);
+            if (procfs_fd < 0)
+                perror("Failed to open /proc");
+            else
+                set_procfs_fd(procfs_fd);
+            free(procdir_path);
 
-			if (chroot("."))
-				gui_fatal("Error chroot to %s", incoming_dir);
-			if (setuid(uid) < 0) {
-				/* no kdialog inside chroot */
-				perror("setuid");
-				exit(1);
-			}
-			return do_unpack();
-	}
-	if (waitpid(pid, &ret, 0) < 0) {
-		gui_fatal("Failed to wait for child process");
-	}
-	if (umount2(".", MNT_DETACH) < 0)
-		gui_fatal("Cannot umount incoming directory");
-	if (!WIFEXITED(ret)) {
-		gui_fatal("Child process exited abnormally");
-	}
-	return WEXITSTATUS(ret);
+            if (chroot("."))
+                gui_fatal("Error chroot to %s", incoming_dir);
+            if (setuid(uid) < 0) {
+                /* no kdialog inside chroot */
+                perror("setuid");
+                exit(1);
+            }
+            return do_unpack();
+    }
+    if (waitpid(pid, &ret, 0) < 0) {
+        gui_fatal("Failed to wait for child process");
+    }
+    if (umount2(".", MNT_DETACH) < 0)
+        gui_fatal("Cannot umount incoming directory");
+    if (!WIFEXITED(ret)) {
+        gui_fatal("Child process exited abnormally");
+    }
+    return WEXITSTATUS(ret);
 }

+ 93 - 69
qubes-rpc/qopen-in-vm.c

@@ -9,100 +9,124 @@
 #include <stdlib.h>
 #include <libqubes-rpc-filecopy.h>
 #include <unistd.h>
+#include <getopt.h>
 #include <gui-fatal.h>
 #include "dvm2.h"
 
-void send_file(const char *fname)
+void send_file(const char *fname, int view_only)
 {
-	const char *base;
-	char sendbuf[DVM_FILENAME_SIZE];
-	int fd = open(fname, O_RDONLY);
-	if (fd < 0)
-		gui_fatal("open %s", fname);
-	base = rindex(fname, '/');
-	if (!base)
-		base = fname;
-	else
-		base++;
-	if (strlen(base) >= DVM_FILENAME_SIZE)
-		base += strlen(base) - DVM_FILENAME_SIZE + 1;
-        strncpy(sendbuf,base,DVM_FILENAME_SIZE - 1); /* fills out with NULs */
-        sendbuf[DVM_FILENAME_SIZE - 1] = '\0';
-	if (!write_all(1, sendbuf, DVM_FILENAME_SIZE))
-		gui_fatal("send filename to dispVM");
-	if (!copy_fd_all(1, fd))
-		gui_fatal("send file to dispVM");
-	close(1);
-	close(fd);
+    const char *base;
+    char sendbuf[DVM_FILENAME_SIZE] = {0};
+    size_t sendbuf_size = DVM_FILENAME_SIZE;
+    int fd = open(fname, O_RDONLY);
+    if (fd < 0)
+        gui_fatal("open %s", fname);
+    if (view_only) {
+        strncpy(sendbuf, DVM_VIEW_ONLY_PREFIX, sendbuf_size);
+        sendbuf_size -= strlen(DVM_VIEW_ONLY_PREFIX);
+    }
+    base = rindex(fname, '/');
+    if (!base)
+        base = fname;
+    else
+        base++;
+    if (strlen(base) >= sendbuf_size)
+        base += strlen(base) - sendbuf_size + 1;
+    strncat(sendbuf,base,sendbuf_size - 1); /* fills out with NULs */
+    sendbuf[DVM_FILENAME_SIZE - 1] = '\0';
+    if (!write_all(1, sendbuf, DVM_FILENAME_SIZE))
+        gui_fatal("send filename to dispVM");
+    if (!copy_fd_all(1, fd))
+        gui_fatal("send file to dispVM");
+    close(1);
+    close(fd);
 }
 
 int copy_and_return_nonemptiness(int tmpfd)
 {
-	struct stat st;
-	if (!copy_fd_all(tmpfd, 0))
-		gui_fatal("receiving file from dispVM");
-	if (fstat(tmpfd, &st))
-		gui_fatal("fstat");
-	close(tmpfd);
+    struct stat st;
+    if (!copy_fd_all(tmpfd, 0))
+        gui_fatal("receiving file from dispVM");
+    if (fstat(tmpfd, &st))
+        gui_fatal("fstat");
+    close(tmpfd);
 
-	return st.st_size > 0;
+    return st.st_size > 0;
 }
 
 void recv_file_nowrite(const char *fname)
 {
-	char *tempfile;
-	char *errmsg;
-	int tmpfd = -1;
+    char *tempfile;
+    char *errmsg;
+    int tmpfd = -1;
 
-	if (asprintf(&tempfile, "/tmp/file_edited_in_dvm.XXXXXX") != -1)
-		tmpfd = mkstemp(tempfile);
-	if (tmpfd < 0)
-		gui_fatal("unable to create any temporary file, aborting");
-	if (!copy_and_return_nonemptiness(tmpfd)) {
-		unlink(tempfile);
-		return;
-	}
-	if (asprintf(&errmsg,
-		 "The file %s has been edited in Disposable VM and the modified content has been received, "
-		 "but this file is in nonwritable directory and thus cannot be modified safely. The edited file has been "
-		 "saved to %s", fname, tempfile) != -1)
+    if (asprintf(&tempfile, "/tmp/file_edited_in_dvm.XXXXXX") != -1)
+        tmpfd = mkstemp(tempfile);
+    if (tmpfd < 0)
+        gui_fatal("unable to create any temporary file, aborting");
+    if (!copy_and_return_nonemptiness(tmpfd)) {
+        unlink(tempfile);
+        return;
+    }
+    if (asprintf(&errmsg,
+         "The file %s has been edited in Disposable VM and the modified content has been received, "
+         "but this file is in nonwritable directory and thus cannot be modified safely. The edited file has been "
+         "saved to %s", fname, tempfile) != -1)
         gui_nonfatal(errmsg);
 }
 
 void actually_recv_file(const char *fname, const char *tempfile, int tmpfd)
 {
-	if (!copy_and_return_nonemptiness(tmpfd)) {
-		unlink(tempfile);
-		return;
-	}
-	if (rename(tempfile, fname))
-		gui_fatal("rename");
+    if (!copy_and_return_nonemptiness(tmpfd)) {
+        unlink(tempfile);
+        return;
+    }
+    if (rename(tempfile, fname))
+        gui_fatal("rename");
 }
 
 void recv_file(const char *fname)
 {
-	int tmpfd = -1;
-	char *tempfile;
-	if (asprintf(&tempfile, "%s.XXXXXX", fname) != -1) {
-		tmpfd = mkstemp(tempfile);
-	}
-	if (tmpfd < 0)
-		recv_file_nowrite(fname);
-	else
-		actually_recv_file(fname, tempfile, tmpfd);
-}
-
-void talk_to_daemon(const char *fname)
-{
-	send_file(fname);
-	recv_file(fname);
+    int tmpfd = -1;
+    char *tempfile;
+    if (asprintf(&tempfile, "%s.XXXXXX", fname) != -1) {
+        tmpfd = mkstemp(tempfile);
+    }
+    if (tmpfd < 0)
+        recv_file_nowrite(fname);
+    else
+        actually_recv_file(fname, tempfile, tmpfd);
 }
 
 int main(int argc, char ** argv)
 {
-	signal(SIGPIPE, SIG_IGN);
-	if (argc!=2)
-		gui_fatal("OpenInVM - no file given?");
-	talk_to_daemon(argv[1]);
-	return 0;
+    char *fname;
+    int view_only = 0;
+    int ret;
+    const struct option opts[] = {
+        {"view-only", no_argument, &view_only, 1},
+        {0}
+    };
+
+    while ((ret=getopt_long(argc, argv, "", opts, NULL)) != -1) {
+        if (ret == '?') {
+            exit(2);
+        }
+    }
+
+    signal(SIGPIPE, SIG_IGN);
+
+    if (optind >= argc)
+        gui_fatal("OpenInVM - no file given?");
+    fname = argv[optind];
+    send_file(fname, view_only);
+    if (!view_only) {
+        recv_file(fname);
+    } else {
+        /* discard received data */
+        int null_fd = open("/dev/null", O_WRONLY);
+        copy_fd_all(null_fd, 0);
+        close(null_fd);
+    }
+    return 0;
 }

+ 6 - 0
qubes-rpc/qvm-actions.sh

@@ -45,6 +45,12 @@ case "$action" in
             qvm-open-in-dvm "$file" | zenity --notification --text "Opening $file in DisposableVM..." --timeout 3 &
         done
         ;;
+    viewdvm)
+        for file in "$@"
+        do
+            qvm-open-in-dvm --view-only "$file" | zenity --notification --text "Opening $file in DisposableVM..." --timeout 3 &
+        done
+        ;;
     *)
         echo "Unknown action. Aborting..."
         exit 1

+ 7 - 2
qubes-rpc/qvm-dvm.desktop

@@ -1,10 +1,15 @@
 [Desktop Entry]
-Actions=QvmDvm;
+Actions=QvmDvm;QvmViewDvm
 Type=Service
 X-KDE-ServiceTypes=KonqPopupMenu/Plugin,all/allfiles
 
 [Desktop Action QvmDvm]
 Exec=/usr/bin/qvm-open-in-dvm %U
 Icon=kget
-Name=Open In DisposableVM
+Name=Edit In DisposableVM
+
+[Desktop Action QvmViewDvm]
+Exec=/usr/bin/qvm-open-in-dvm --view-only %U
+Icon=kget
+Name=View In DisposableVM
 

+ 3 - 3
qubes-rpc/qvm-open-in-dvm

@@ -20,10 +20,10 @@
 #
 #
 
-if ! [ $# = 1 ] ; then
-	echo "Usage: $0 filename"
+if ! [ $# = 1 ] && ! [ $# = 2 ]; then
+	echo "Usage: $0 [--view-only] filename"
 	exit 1
 fi
 
 # shellcheck disable=SC2016
-exec qvm-open-in-vm '$dispvm' "$1"
+exec qvm-open-in-vm '$dispvm' "$@"

+ 27 - 6
qubes-rpc/qvm-open-in-vm

@@ -20,16 +20,37 @@
 #
 #
 
-if ! [ $# = 2 ] ; then
-	echo "Usage: $0 vmname filename"
-	exit 1
+usage() {
+	echo "Usage: $0 [--view-only] vmname filename"
+	exit 2
+}
+
+qopen_opts=
+target=
+filename=
+
+while [ $# -gt 0 ]; do
+    if [ "x$1" = "x--view-only" ]; then
+        qopen_opts=--view-only
+    elif [ -z "$target" ]; then
+        target="$1"
+    elif [ -z "$filename" ]; then
+        filename="$1"
+    else
+        usage
+    fi
+    shift
+done
+
+if [ -z "$target" ] || [ -z "$filename" ]; then
+    usage
 fi
 
-case "$2" in
+case "$filename" in
 	*://*)
-        exec /usr/lib/qubes/qrexec-client-vm "$1" qubes.OpenURL /bin/echo "$2"
+        exec /usr/lib/qubes/qrexec-client-vm "$target" qubes.OpenURL /bin/echo "$filename"
         ;;
     *)
-        exec /usr/lib/qubes/qrexec-client-vm "$1" qubes.OpenInVM "/usr/lib/qubes/qopen-in-vm" "$2"
+        exec /usr/lib/qubes/qrexec-client-vm "$target" qubes.OpenInVM "/usr/lib/qubes/qopen-in-vm" $qopen_opts "$filename"
         ;;
 esac

+ 20 - 6
qubes-rpc/qvm_dvm_nautilus.py

@@ -17,15 +17,24 @@ class OpenInDvmItemExtension(GObject.GObject, Nautilus.MenuProvider):
         if not files:
             return
 
-        menu_item = Nautilus.MenuItem(name='QubesMenuProvider::OpenInDvm',
-                                      label='Open In DisposableVM',
+        menu_item1 = Nautilus.MenuItem(name='QubesMenuProvider::OpenInDvm',
+                                      label='Edit In DisposableVM',
                                       tip='',
                                       icon='')
 
-        menu_item.connect('activate', self.on_menu_item_clicked, files)
-        return menu_item,
+        menu_item1.connect('activate', self.on_menu_item_clicked, files)
 
-    def on_menu_item_clicked(self, menu, files):
+        menu_item2 = Nautilus.MenuItem(name='QubesMenuProvider::ViewInDvm',
+                                      label='View In DisposableVM',
+                                      tip='',
+                                      icon='')
+
+        menu_item2.connect('activate',
+                self.on_menu_item_clicked,
+                files, True)
+        return menu_item1, menu_item2,
+
+    def on_menu_item_clicked(self, menu, files, view_only=False):
         '''Called when user chooses files though Nautilus context menu.
         '''
         for file_obj in files:
@@ -38,6 +47,11 @@ class OpenInDvmItemExtension(GObject.GObject, Nautilus.MenuProvider):
 
             # Use subprocess.DEVNULL in python >= 3.3
             devnull = open(os.devnull, 'wb')
+            command = ['nohup', '/usr/bin/qvm-open-in-dvm']
+            if view_only:
+                command.append('--view-only')
+            command.append(gio_file.get_path())
 
             # Use Popen instead of subprocess.call to spawn the process
-            Popen(['nohup', '/usr/bin/qvm-open-in-dvm', gio_file.get_path()], stdout=devnull, stderr=devnull)
+            Popen(command, stdout=devnull, stderr=devnull)
+            devnull.close()

+ 189 - 179
qubes-rpc/vm-file-editor.c

@@ -19,217 +19,227 @@ static const char *cleanup_dirname = NULL;
 
 static void cleanup_file(void)
 {
-	if (cleanup_filename) {
-		if (unlink(cleanup_filename) < 0)
-			fprintf(stderr, "Failed to remove file at exit\n");
-		cleanup_filename = NULL;
-	}
-	if (cleanup_dirname) {
-		if (rmdir(cleanup_dirname) < 0)
-			fprintf(stderr, "Failed to remove directory at exit\n");
-		cleanup_dirname = NULL;
-	}
+    if (cleanup_filename) {
+        if (unlink(cleanup_filename) < 0)
+            fprintf(stderr, "Failed to remove file at exit\n");
+        cleanup_filename = NULL;
+    }
+    if (cleanup_dirname) {
+        if (rmdir(cleanup_dirname) < 0)
+            fprintf(stderr, "Failed to remove directory at exit\n");
+        cleanup_dirname = NULL;
+    }
 }
 
 const char *gettime(void)
 {
-	static char retbuf[60];
-	struct timeval tv;
-	gettimeofday(&tv, NULL);
-	snprintf(retbuf, sizeof(retbuf), "%lld.%06lld",
-		 (long long) tv.tv_sec, (long long) tv.tv_usec);
-	return retbuf;
+    static char retbuf[60];
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    snprintf(retbuf, sizeof(retbuf), "%lld.%06lld",
+         (long long) tv.tv_sec, (long long) tv.tv_usec);
+    return retbuf;
 }
 
 static char *get_directory(void)
 {
-	const char *remote_domain;
-	char *dir;
-	size_t len;
-	char *ret;
-
-	remote_domain = getenv("QREXEC_REMOTE_DOMAIN");
-	if (!remote_domain) {
-		fprintf(stderr, "Cannot get remote domain name\n");
-		exit(1);
-	}
-	if (!*remote_domain || index(remote_domain, '/'))
-		goto fail;
-	if (!strcmp(remote_domain, ".") || !strcmp(remote_domain, ".."))
-		goto fail;
-
-	len = strlen("/tmp/-XXXXXX")+strlen(remote_domain)+1;
-	dir = malloc(len);
-	if (!dir) {
-		fprintf(stderr, "Cannot allocate memory\n");
-		exit(1);
-	}
-	snprintf(dir, len, "/tmp/%s-XXXXXX", remote_domain);
-
-	ret = mkdtemp(dir);
-	if (ret == NULL) {
-		perror("mkdtemp");
-		exit(1);
-	}
-	cleanup_dirname = strdup(ret);
-	return ret;
+    const char *remote_domain;
+    char *dir;
+    size_t len;
+    char *ret;
+
+    remote_domain = getenv("QREXEC_REMOTE_DOMAIN");
+    if (!remote_domain) {
+        fprintf(stderr, "Cannot get remote domain name\n");
+        exit(1);
+    }
+    if (!*remote_domain || index(remote_domain, '/'))
+        goto fail;
+    if (!strcmp(remote_domain, ".") || !strcmp(remote_domain, ".."))
+        goto fail;
+
+    len = strlen("/tmp/-XXXXXX")+strlen(remote_domain)+1;
+    dir = malloc(len);
+    if (!dir) {
+        fprintf(stderr, "Cannot allocate memory\n");
+        exit(1);
+    }
+    snprintf(dir, len, "/tmp/%s-XXXXXX", remote_domain);
+
+    ret = mkdtemp(dir);
+    if (ret == NULL) {
+        perror("mkdtemp");
+        exit(1);
+    }
+    cleanup_dirname = strdup(ret);
+    return ret;
 
 fail:
-	fprintf(stderr, "Invalid remote domain name: %s\n", remote_domain);
-	exit(1);
+    fprintf(stderr, "Invalid remote domain name: %s\n", remote_domain);
+    exit(1);
 }
 
-char *get_filename(void)
+char *get_filename(int *view_only)
 {
-	char buf[DVM_FILENAME_SIZE];
-	static char *retname;
-	int i;
-	char *directory;
-	size_t len;
-
-	directory = get_directory();
-	if (!read_all(0, buf, sizeof(buf)))
-		exit(1);
-	buf[DVM_FILENAME_SIZE-1] = 0;
-	if (index(buf, '/')) {
-		fprintf(stderr, "filename contains /");
-		exit(1);
-	}
-	for (i=0; buf[i]!=0; i++) {
-		// replace some characters with _ (eg mimeopen have problems with some of them)
-		if (index(" !?\"#$%^&*()[]<>;`~|", buf[i]))
-			buf[i]='_';
-	}
-	len = strlen(directory)+1+strlen(buf)+1;
-	retname = malloc(len);
-	if (!retname) {
-		fprintf(stderr, "Cannot allocate memory\n");
-		exit(1);
-	}
-	snprintf(retname, len, "%s/%s", directory, buf);
-	free(directory);
-	return retname;
+    char buf[DVM_FILENAME_SIZE];
+    char *fname = buf;
+    static char *retname;
+    int i;
+    char *directory;
+    size_t len;
+
+    directory = get_directory();
+    if (!read_all(0, buf, sizeof(buf)))
+        exit(1);
+    buf[DVM_FILENAME_SIZE-1] = 0;
+    if (index(buf, '/')) {
+        fprintf(stderr, "filename contains /");
+        exit(1);
+    }
+    for (i=0; buf[i]!=0; i++) {
+        // replace some characters with _ (eg mimeopen have problems with some of them)
+        if (index(" !?\"#$%^&*()[]<>;`~|", buf[i]))
+            buf[i]='_';
+    }
+    if (strncmp(buf, DVM_VIEW_ONLY_PREFIX, strlen(DVM_VIEW_ONLY_PREFIX)) == 0) {
+        *view_only = 1;
+        fname += strlen(DVM_VIEW_ONLY_PREFIX);
+    }
+    len = strlen(directory)+1+strlen(fname)+1;
+    retname = malloc(len);
+    if (!retname) {
+        fprintf(stderr, "Cannot allocate memory\n");
+        exit(1);
+    }
+    snprintf(retname, len, "%s/%s", directory, fname);
+    free(directory);
+    return retname;
 }
 
 void copy_file_by_name(const char *filename)
 {
-	int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0600);
-	if (fd < 0) {
-		perror("open file");
-		exit(1);
-	}
-	/* we now have created a new file, ensure we delete it at the end */
-	cleanup_filename = strdup(filename);
-	atexit(cleanup_file);
-	if (!copy_fd_all(fd, 0))
+    int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0600);
+    if (fd < 0) {
+        perror("open file");
+        exit(1);
+    }
+    /* we now have created a new file, ensure we delete it at the end */
+    cleanup_filename = strdup(filename);
+    atexit(cleanup_file);
+    if (!copy_fd_all(fd, 0))
         exit(1);
-	close(fd);
+    close(fd);
 }
 
 void send_file_back(const char * filename)
 {
-	int fd = open(filename, O_RDONLY);
-	if (fd < 0) {
-		perror("open file");
-		exit(1);
-	}
-	if (!copy_fd_all(1, fd))
-	 exit(1);
-	close(fd);
-	close(1);
+    int fd = open(filename, O_RDONLY);
+    if (fd < 0) {
+        perror("open file");
+        exit(1);
+    }
+    if (!copy_fd_all(1, fd))
+     exit(1);
+    close(fd);
+    close(1);
 }
 
 int
 main()
 {
-	struct stat stat_pre, stat_post, session_stat;
-	char *filename = get_filename();
-	int child, status, log_fd, null_fd;
-	FILE *waiter_pidfile;
-
-	copy_file_by_name(filename);
-	if (stat(filename, &stat_pre)) {
-		perror("stat pre");
-		exit(1);
-	}
+    struct stat stat_pre, stat_post, session_stat;
+    int view_only = 0;
+    char *filename = get_filename(&view_only);
+    int child, status, log_fd, null_fd;
+    FILE *waiter_pidfile;
+
+    copy_file_by_name(filename);
+    if (view_only) {
+        // mark file as read-only so applications will signal it to the user
+        chmod(filename, 0400);
+    }
+    if (stat(filename, &stat_pre)) {
+        perror("stat pre");
+        exit(1);
+    }
 #ifdef DEBUG
-	fprintf(stderr, "time=%s, waiting for qubes-session\n", gettime());
+    fprintf(stderr, "time=%s, waiting for qubes-session\n", gettime());
 #endif
-	// wait for X server to starts (especially in DispVM)
-	if (stat("/tmp/qubes-session-env", &session_stat)) {
-		switch (child = fork()) {
-			case -1:
-				perror("fork");
-				exit(1);
-			case 0:
-				waiter_pidfile = fopen("/tmp/qubes-session-waiter", "a");
-				if (waiter_pidfile == NULL) {
-					perror("fopen waiter_pidfile");
-					exit(1);
-				}
-				fprintf(waiter_pidfile, "%d\n", getpid());
-				fclose(waiter_pidfile);
-				// check the second time, to prevent race
-				if (stat("/tmp/qubes-session-env", &session_stat)) {
-					// wait for qubes-session notify
-					pause();
-				}
-				exit(0);
-			default:
-				waitpid(child, &status, 0);
-				if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
-					//propagate exit code from child
-					exit(WEXITSTATUS(status));
-				}
-		}
-	}
+    // wait for X server to starts (especially in DispVM)
+    if (stat("/tmp/qubes-session-env", &session_stat)) {
+        switch (child = fork()) {
+            case -1:
+                perror("fork");
+                exit(1);
+            case 0:
+                waiter_pidfile = fopen("/tmp/qubes-session-waiter", "a");
+                if (waiter_pidfile == NULL) {
+                    perror("fopen waiter_pidfile");
+                    exit(1);
+                }
+                fprintf(waiter_pidfile, "%d\n", getpid());
+                fclose(waiter_pidfile);
+                // check the second time, to prevent race
+                if (stat("/tmp/qubes-session-env", &session_stat)) {
+                    // wait for qubes-session notify
+                    pause();
+                }
+                exit(0);
+            default:
+                waitpid(child, &status, 0);
+                if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
+                    //propagate exit code from child
+                    exit(WEXITSTATUS(status));
+                }
+        }
+    }
 #ifdef DEBUG
-	fprintf(stderr, "time=%s, starting editor\n", gettime());
+    fprintf(stderr, "time=%s, starting editor\n", gettime());
 #endif
-	switch (child = fork()) {
-		case -1:
-			perror("fork");
-			exit(1);
-		case 0:
-			null_fd = open("/dev/null", O_RDONLY);
-			dup2(null_fd, 0);
-			close(null_fd);
-
-			log_fd = open("/tmp/mimeopen.log", O_CREAT | O_APPEND, 0666);
-			if (log_fd == -1) {
-				perror("open /tmp/mimeopen.log");
-				exit(1);
-			}
-			dup2(log_fd, 1);
-			close(log_fd);
-
-			setenv("HOME", USER_HOME, 1);
-			setenv("DISPLAY", ":0", 1);
-			execl("/usr/bin/qubes-open", "qubes-open", filename, (char*)NULL);
-			perror("execl");
-			exit(1);
-		default:
-			waitpid(child, &status, 0);
-			if (status != 0) {
-				char cmd[512];
+    switch (child = fork()) {
+        case -1:
+            perror("fork");
+            exit(1);
+        case 0:
+            null_fd = open("/dev/null", O_RDONLY);
+            dup2(null_fd, 0);
+            close(null_fd);
+
+            log_fd = open("/tmp/mimeopen.log", O_CREAT | O_APPEND, 0666);
+            if (log_fd == -1) {
+                perror("open /tmp/mimeopen.log");
+                exit(1);
+            }
+            dup2(log_fd, 1);
+            close(log_fd);
+
+            setenv("HOME", USER_HOME, 1);
+            setenv("DISPLAY", ":0", 1);
+            execl("/usr/bin/qubes-open", "qubes-open", filename, (char*)NULL);
+            perror("execl");
+            exit(1);
+        default:
+            waitpid(child, &status, 0);
+            if (status != 0) {
+                char cmd[512];
 #ifdef USE_KDIALOG
-				snprintf(cmd, sizeof(cmd),
-						"HOME=/home/user DISPLAY=:0 /usr/bin/kdialog --sorry 'Unable to handle mimetype of the requested file (exit status: %d)!' > /tmp/kdialog.log 2>&1 </dev/null", status);
-					("HOME=/home/user DISPLAY=:0 /usr/bin/kdialog --sorry 'Unable to handle mimetype of the requested file (exit status: %d)!' > /tmp/kdialog.log 2>&1 </dev/null", status);
+                snprintf(cmd, sizeof(cmd),
+                        "HOME=/home/user DISPLAY=:0 /usr/bin/kdialog --sorry 'Unable to handle mimetype of the requested file (exit status: %d)!' > /tmp/kdialog.log 2>&1 </dev/null", status);
+                    ("HOME=/home/user DISPLAY=:0 /usr/bin/kdialog --sorry 'Unable to handle mimetype of the requested file (exit status: %d)!' > /tmp/kdialog.log 2>&1 </dev/null", status);
 #else
-				snprintf(cmd, sizeof(cmd),
-						"HOME=/home/user DISPLAY=:0 /usr/bin/zenity --error --text 'Unable to handle mimetype of the requested file (exit status: %d)!' > /tmp/kdialog.log 2>&1 </dev/null", status);
+                snprintf(cmd, sizeof(cmd),
+                        "HOME=/home/user DISPLAY=:0 /usr/bin/zenity --error --text 'Unable to handle mimetype of the requested file (exit status: %d)!' > /tmp/kdialog.log 2>&1 </dev/null", status);
 #endif
-				status = system(cmd);
-			}
-	}
-
-	if (stat(filename, &stat_post)) {
-		perror("stat post");
-		exit(1);
-	}
-	if (stat_pre.st_mtime != stat_post.st_mtime)
-		send_file_back(filename);
-	free(filename);
-	return 0;
+                status = system(cmd);
+            }
+    }
+
+    if (stat(filename, &stat_post)) {
+        perror("stat post");
+        exit(1);
+    }
+    if (stat_pre.st_mtime != stat_post.st_mtime)
+        send_file_back(filename);
+    free(filename);
+    return 0;
 }