diff --git a/criu/file-lock.c b/criu/file-lock.c index 834594e4a..8ccebd033 100644 --- a/criu/file-lock.c +++ b/criu/file-lock.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -19,6 +20,8 @@ #include "proc_parse.h" #include "servicefd.h" #include "file-lock.h" +#include "pstree.h" +#include "files-reg.h" struct file_lock_rst { FileLockEntry *fle; @@ -103,6 +106,8 @@ int dump_file_locks(void) continue; } + if (fl->fl_kind == FL_LEASE && !fl->updated) + continue; file_lock_entry__init(&fle); fle.pid = fl->real_owner; @@ -333,6 +338,73 @@ int note_file_lock(struct pid *pid, int fd, int lfd, struct fd_parms *p) return 0; } +int correct_file_leases_type(struct pid *pid, int fd, int lfd) +{ + struct file_lock *fl; + int target_type; + + list_for_each_entry(fl, &file_lock_list, list) { + /* owners_fd should be set before usage */ + if (fl->fl_owner != pid->real || fl->owners_fd != fd) + continue; + + fl->updated = true; + if (fl->fl_kind == FL_LEASE && + (fl->fl_ltype & LEASE_BREAKING)) { + /* + * Set lease type to actual 'target lease type' + * instead of 'READ' returned by procfs. + */ + target_type = fcntl(lfd, F_GETLEASE); + if (target_type < 0) { + perror("Can't get lease type\n"); + return -1; + } + fl->fl_ltype &= ~O_ACCMODE; + fl->fl_ltype |= target_type; + break; + } + } + return 0; +} + +static int open_break_cb(int ns_root_fd, struct reg_file_info *rfi, void *arg) +{ + int fd, flags = *(int *)arg | O_NONBLOCK; + + fd = openat(ns_root_fd, rfi->path, flags); + if (fd >= 0) { + pr_err("Conflicting lease wasn't found\n"); + close(fd); + return -1; + } else if (errno != EWOULDBLOCK) { + pr_perror("Can't break lease\n"); + return -1; + } + return 0; +} + +static int break_lease(int lease_type, struct file_desc *desc) +{ + int target_type = lease_type & (~LEASE_BREAKING); + int break_flags; + + /* + * Flags for open call chosen in a way to even + * 'target lease type' returned by fcntl(F_GETLEASE) + * and lease type from the image. + */ + if (target_type == F_UNLCK) { + break_flags = O_WRONLY; + } else if (target_type == F_RDLCK) { + break_flags = O_RDONLY; + } else { + pr_err("Incorrect target lease type\n"); + return -1; + } + return open_path(desc, open_break_cb, (void *)&break_flags); +} + static int set_file_lease(int fd, int type) { int old_fsuid, ret; @@ -357,6 +429,90 @@ static int set_file_lease(int fd, int type) return ret; } +static int restore_lease_prebreaking_state(int fd, int fd_type) +{ + int access_flags = fd_type & O_ACCMODE; + int lease_type = (access_flags == O_RDONLY) ? F_RDLCK : F_WRLCK; + + return set_file_lease(fd, lease_type); +} + +static struct fdinfo_list_entry *find_fd_unordered(struct pstree_item *task, + int fd) +{ + struct list_head *head = &rsti(task)->fds; + struct fdinfo_list_entry *fle; + + list_for_each_entry_reverse(fle, head, ps_list) { + if (fle->fe->fd == fd) + return fle; + } + return NULL; +} + +static int restore_breaking_file_lease(FileLockEntry *fle) +{ + struct fdinfo_list_entry *fdle; + int ret; + + fdle = find_fd_unordered(current, fle->fd); + if (fdle == NULL) { + pr_err("Can't get file description\n"); + return -1; + } + + ret = restore_lease_prebreaking_state(fle->fd, fdle->desc->ops->type); + if (ret) + return ret; + + /* + * It could be broken by 2 types of open call: + * 1. non-blocking: It failed because of the lease. + * 2. blocking: It had been blocked at the moment + * of dumping, otherwise lease wouldn't be broken. + * Thus, it was canceled by CRIU. + * + * There are no files or leases in image, which will + * conflict with each other. Therefore we should explicitly + * break leases. Restoring can be done in any order. + */ + return break_lease(fle->type, fdle->desc); +} + +static int restore_file_lease(FileLockEntry *fle) +{ + sigset_t blockmask, oldmask; + int signum_fcntl, signum, ret; + + if (fle->type & LEASE_BREAKING) { + signum_fcntl = fcntl(fle->fd, F_GETSIG); + signum = signum_fcntl ? signum_fcntl : SIGIO; + if (signum_fcntl < 0) { + pr_perror("Can't get file i/o signum\n"); + return -1; + } + if (sigemptyset(&blockmask) || + sigaddset(&blockmask, signum) || + sigprocmask(SIG_BLOCK, &blockmask, &oldmask)) { + pr_perror("Can't block file i/o signal\n"); + return -1; + } + + ret = restore_breaking_file_lease(fle); + + if (sigprocmask(SIG_SETMASK, &oldmask, NULL)) { + pr_perror("Can't restore sigmask\n"); + ret = -1; + } + return ret; + } else { + ret = set_file_lease(fle->fd, fle->type); + if (ret < 0) + pr_perror("Can't restore non breaking lease"); + return ret; + } +} + static int restore_file_lock(FileLockEntry *fle) { int ret = -1; @@ -428,11 +584,9 @@ static int restore_file_lock(FileLockEntry *fle) "start: %8"PRIx64", len: %8"PRIx64"\n", fle->flag, fle->type, fle->pid, fle->fd, fle->start, fle->len); - ret = set_file_lease(fle->fd, fle->type); - if (ret < 0) { - pr_perror("Can't set lease!\n"); + ret = restore_file_lease(fle); + if (ret < 0) goto err; - } } else { pr_err("Unknown file lock style!\n"); goto err; diff --git a/criu/files.c b/criu/files.c index 7ac8a2e00..90dd1b0ec 100644 --- a/criu/files.c +++ b/criu/files.c @@ -474,6 +474,9 @@ static int dump_one_file(struct pid *pid, int fd, int lfd, struct fd_opts *opts, if (note_file_lock(pid, fd, lfd, &p)) return -1; + if (correct_file_leases_type(pid, fd, lfd)) + return -1; + p.fd_ctl = ctl; /* Some dump_opts require this to talk to parasite */ if (S_ISSOCK(p.stat.st_mode)) diff --git a/criu/include/file-lock.h b/criu/include/file-lock.h index f70739adb..14a33b436 100644 --- a/criu/include/file-lock.h +++ b/criu/include/file-lock.h @@ -38,6 +38,9 @@ #define LOCK_WRITE 128 /* which allows concurrent write operations */ #define LOCK_RW 192 /* which allows concurrent read & write ops */ +/* for leases */ +#define LEASE_BREAKING 4 + struct file_lock { long long fl_id; int fl_kind; @@ -53,6 +56,8 @@ struct file_lock { int real_owner; int owners_fd; + + bool updated; /* used to remove duplicate leases */ }; extern struct list_head file_lock_list; @@ -65,6 +70,7 @@ extern struct collect_image_info file_locks_cinfo; struct pid; struct fd_parms; +extern int correct_file_leases_type(struct pid *, int fd, int lfd); extern int note_file_lock(struct pid *, int fd, int lfd, struct fd_parms *); extern int dump_file_locks(void); diff --git a/criu/proc_parse.c b/criu/proc_parse.c index 127b2ca97..9778ec49f 100644 --- a/criu/proc_parse.c +++ b/criu/proc_parse.c @@ -2008,6 +2008,8 @@ static int parse_file_lock_buf(char *buf, struct file_lock *fl, return -1; } + fl->updated = false; + if (!strcmp(fl_flag, "POSIX")) fl->fl_kind = FL_POSIX; else if (!strcmp(fl_flag, "FLOCK")) @@ -2019,6 +2021,10 @@ static int parse_file_lock_buf(char *buf, struct file_lock *fl, else fl->fl_kind = FL_UNKNOWN; + if (fl->fl_kind == FL_LEASE && !strcmp(fl_type, "BREAKING")) { + fl->fl_ltype |= LEASE_BREAKING; + } + if (!strcmp(fl_type, "MSNFS")) { fl->fl_ltype |= LOCK_MAND; @@ -2032,9 +2038,6 @@ static int parse_file_lock_buf(char *buf, struct file_lock *fl, pr_err("Unknown lock option!\n"); return -1; } - } else if (fl->fl_kind == FL_LEASE && !strcmp(fl_type, "BREAKING")) { - pr_err("Breaking leases are not supported (%d): %s\n", - num, buf); } else { if (!strcmp(fl_option, "UNLCK")) { fl->fl_ltype |= F_UNLCK;