* [PATCH v21 020/100] c/r: documentation
[not found] <1272723382-19470-1-git-send-email-orenl@cs.columbia.edu>
@ 2010-05-01 14:15 ` Oren Laadan
2010-05-06 20:27 ` Randy Dunlap
2010-05-01 14:15 ` [PATCH v21 022/100] c/r: basic infrastructure for checkpoint/restart Oren Laadan
` (12 subsequent siblings)
13 siblings, 1 reply; 16+ messages in thread
From: Oren Laadan @ 2010-05-01 14:15 UTC (permalink / raw)
To: Andrew Morton
Cc: containers, linux-kernel, Serge Hallyn, Matt Helsley,
Pavel Emelyanov, Oren Laadan, linux-api, linux-mm, linux-fsdevel,
netdev, Dave Hansen
Covers application checkpoint/restart, overall design, interfaces,
usage, shared objects, and and checkpoint image format.
Changelog[v19-rc1]:
- Update documentation and examples for new syscalls API
- [Liu Alexander] Fix typos
- [Serge Hallyn] Update checkpoint image format
Changelog[v16]:
- Update documentation
- Unify into readme.txt and usage.txt
Changelog[v14]:
- Discard the 'h.parent' field
- New image format (shared objects appear before they are referenced
unless they are compound)
Changelog[v8]:
- Split into multiple files in Documentation/checkpoint/...
- Extend documentation, fix typos and comments from feedback
Cc: linux-api@vger.kernel.org
Cc: linux-mm@kvack.org
Cc: linux-fsdevel@vger.kernel.org
Cc: netdev@vger.kernel.org
Signed-off-by: Oren Laadan <orenl@cs.columbia.edu>
Signed-off-by: Dave Hansen <dave@linux.vnet.ibm.com>
Acked-by: Serge E. Hallyn <serue@us.ibm.com>
Tested-by: Serge E. Hallyn <serue@us.ibm.com>
---
Documentation/checkpoint/checkpoint.c | 38 +++
Documentation/checkpoint/readme.txt | 370 ++++++++++++++++++++++++++++
Documentation/checkpoint/self_checkpoint.c | 69 +++++
Documentation/checkpoint/self_restart.c | 40 +++
Documentation/checkpoint/usage.txt | 247 +++++++++++++++++++
5 files changed, 764 insertions(+), 0 deletions(-)
create mode 100644 Documentation/checkpoint/checkpoint.c
create mode 100644 Documentation/checkpoint/readme.txt
create mode 100644 Documentation/checkpoint/self_checkpoint.c
create mode 100644 Documentation/checkpoint/self_restart.c
create mode 100644 Documentation/checkpoint/usage.txt
diff --git a/Documentation/checkpoint/checkpoint.c b/Documentation/checkpoint/checkpoint.c
new file mode 100644
index 0000000..8560f30
--- /dev/null
+++ b/Documentation/checkpoint/checkpoint.c
@@ -0,0 +1,38 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+
+#include <linux/checkpoint.h>
+
+static inline int checkpoint(pid_t pid, int fd, unsigned long flags)
+{
+ return syscall(__NR_checkpoint, pid, fd, flags);
+}
+
+int main(int argc, char *argv[])
+{
+ pid_t pid;
+ int ret;
+
+ if (argc != 2) {
+ printf("usage: ckpt PID\n");
+ exit(1);
+ }
+
+ pid = atoi(argv[1]);
+ if (pid <= 0) {
+ printf("invalid pid\n");
+ exit(1);
+ }
+
+ ret = checkpoint(pid, STDOUT_FILENO, CHECKPOINT_SUBTREE);
+
+ if (ret < 0)
+ perror("checkpoint");
+ else
+ printf("checkpoint id %d\n", ret);
+
+ return (ret > 0 ? 0 : 1);
+}
diff --git a/Documentation/checkpoint/readme.txt b/Documentation/checkpoint/readme.txt
new file mode 100644
index 0000000..4fa5560
--- /dev/null
+++ b/Documentation/checkpoint/readme.txt
@@ -0,0 +1,370 @@
+
+ Checkpoint-Restart support in the Linux kernel
+ ==========================================================
+
+Copyright (C) 2008-2010 Oren Laadan
+
+Author: Oren Laadan <orenl@cs.columbia.edu>
+
+License: The GNU Free Documentation License, Version 1.2
+ (dual licensed under the GPL v2)
+
+Contributors: Oren Laadan <orenl@cs.columbia.edu>
+ Serge Hallyn <serue@us.ibm.com>
+ Dan Smith <danms@us.ibm.com>
+ Matt Helsley <matthltc@us.ibm.com>
+ Nathan Lynch <ntl@pobox.com>
+ Sukadev Bhattiprolu <sukadev@linux.vnet.ibm.com>
+ Dave Hansen <dave@linux.vnet.ibm.com>
+
+
+Introduction
+============
+
+Application checkpoint/restart [C/R] is the ability to save the state
+of a running application so that it can later resume its execution
+from the time at which it was checkpointed. An application can be
+migrated by checkpointing it on one machine and restarting it on
+another. C/R can provide many potential benefits:
+
+* Failure recovery: by rolling back to a previous checkpoint
+
+* Improved response time: by restarting applications from checkpoints
+ instead of from scratch.
+
+* Improved system utilization: by suspending long running CPU
+ intensive jobs and resuming them when load decreases.
+
+* Fault resilience: by migrating applications off faulty hosts.
+
+* Dynamic load balancing: by migrating applications to less loaded
+ hosts.
+
+* Improved service availability and administration: by migrating
+ applications before host maintenance so that they continue to run
+ with minimal downtime
+
+* Time-travel: by taking periodic checkpoints and restarting from
+ any previous checkpoint.
+
+Compared to hypervisor approaches, application C/R is more lightweight
+since it need only save the state associated with applications, while
+operating system data structures (e.g. buffer cache, drivers state
+and the like) are uninteresting.
+
+
+Overall design
+==============
+
+Checkpoint and restart are done in the kernel as much as possible.
+Two new system calls are introduced to provide C/R: sys_checkpoint()
+and sys_restart(). They both operate on a process tree (hierarchy),
+either a whole container or a subtree of a container.
+
+Checkpointing entire containers ensures that there are no dependencies
+on anything outside the container, which guarantees that a matching
+restart will succeed (assuming that the file system state remains
+consistent). However, it requires that users will always run the tasks
+that they wish to checkpoint inside containers. This is ideal for,
+e.g., private virtual servers and the like.
+
+In contrast, when checkpointing a subtree of a container it is up to
+the user to ensure that dependencies either don't exist or can be
+safely ignored. This is useful, for instance, for HPC scenarios or
+even a user that would like to periodically checkpoint a long-running
+batch job.
+
+An additional system call, a la madvise(), is planned, so that tasks
+can advise the kernel how to handle specific resources. For instance,
+a task could ask to skip a memory area at checkpoint to save space,
+or to use a preset file descriptor at restart instead of restoring it
+from the checkpoint image. It will provide the flexibility that is
+particularly useful to address the needs of a diverse crowd of users
+and use-cases.
+
+Syscall sys_checkpoint() is given a pid that indicates the top of the
+hierarchy, a file descriptor to store the image, and flags. The code
+serializes internal user- and kernel-state and writes it out to the
+file descriptor. The resulting image is stream-able. The processes are
+expected to be frozen for the duration of the checkpoint.
+
+In general, a checkpoint consists of 5 steps:
+1. Pre-dump
+2. Freeze the container/subtree
+3. Save tasks' and kernel state <-- sys_checkpoint()
+4. Thaw (or kill) the container/subtree
+5. Post-dump
+
+Step 3 is done by calling sys_checkpoint(). Steps 1 and 5 are an
+optimization to reduce application downtime. In particular, "pre-dump"
+works before freezing the container, e.g. the pre-copy for live
+migration, and "post-dump" works after the container resumes
+execution, e.g. write-back the data to secondary storage.
+
+The kernel exports a relatively opaque 'blob' of data to userspace
+which can then be handed to the new kernel at restart time. The
+'blob' contains data and state of select portions of kernel structures
+such as VMAs and mm_structs, as well as copies of the actual memory
+that the tasks use. Any changes in this blob's format between kernel
+revisions can be handled by an in-userspace conversion program.
+
+To restart, userspace first create a process hierarchy that matches
+that of the checkpoint, and each task calls sys_restart(). The syscall
+reads the saved kernel state from a file descriptor, and re-creates
+the resources that the tasks need to resume execution. The restart
+code is executed by each task that is restored in the new hierarchy to
+reconstruct its own state.
+
+In general, a restart consists of 3 steps:
+1. Create hierarchy
+2. Restore tasks' and kernel state <-- sys_restart()
+3. Resume userspace (or freeze tasks)
+
+Because the process hierarchy, during restart in created in userspace,
+the restarting tasks have the flexibility to prepare before calling
+sys_restart().
+
+
+Checkpoint image format
+=======================
+
+The checkpoint image format is built of records that consist of a
+pre-header identifying its contents, followed by a payload. This
+format allow userspace tools to easily parse and skip through the
+image without requiring intimate knowledge of the data. It will also
+be handy to enable parallel checkpointing in the future where multiple
+threads interleave data from multiple processes into a single stream.
+
+The pre-header is defined by 'struct ckpt_hdr' as follows: @type
+identifies the type of the payload, @len tells its length in bytes
+including the pre-header.
+
+struct ckpt_hdr {
+ __s32 type;
+ __s32 len;
+};
+
+The pre-header must be the first component in all other headers. For
+instance, the task data is saved in 'struct ckpt_hdr_task', which
+looks something like this:
+
+struct ckpt_hdr_task {
+ struct ckpt_hdr h;
+ __u32 pid;
+ ...
+};
+
+THE IMAGE FORMAT IS EXPECTED TO CHANGE over time as more features are
+supported, or as existing features change in the kernel and require to
+adjust their representation. Any such changes will be be handled by
+in-userspace conversion tools.
+
+The general format of the checkpoint image is as follows:
+* Image header
+* Container configuration
+* Task hierarchy
+* Tasks' state
+* Image trailer
+
+The image always begins with a general header that holds a magic
+number, an architecture identifier (little endian format), a format
+version number (@rev), followed by information about the kernel
+(currently version and UTS data). It also holds the time of the
+checkpoint and the flags given to sys_checkpoint(). This header is
+followed by an arch-specific header.
+
+The container configuration section containers information that is
+global to the container. Security (LSM) configuration is one example.
+Network configuration and container-wide mounts may also go here, so
+that the userspace restart coordinator can re-create a suitable
+environment.
+
+The task hierarchy comes next so that userspace tools can read it
+early (even from a stream) and re-create the restarting tasks. This is
+basically an array of all checkpointed tasks, and their relationships
+(parent, siblings, threads, etc).
+
+Then the state of all tasks is saved, in the order that they appear in
+the tasks array above. For each state, we save data like task_struct,
+namespaces, open files, memory layout, memory contents, cpu state,
+signals and signal handlers, etc. For resources that are shared among
+multiple processes, we first checkpoint said resource (and only once),
+and in the task data we give a reference to it. More about shared
+resources below.
+
+Finally, the image always ends with a trailer that holds a (different)
+magic number, serving for sanity check.
+
+
+Shared objects
+==============
+
+Many resources may be shared by multiple tasks (e.g. file descriptors,
+memory address space, etc), or even have multiple references from
+other resources (e.g. a single inode that represents two ends of a
+pipe).
+
+Shared objects are tracked using a hash table (objhash) to ensure that
+they are only checkpointed or restored once. To handle a shared
+object, it is first looked up in the hash table, to determine if is
+the first encounter or a recurring appearance. The hash table itself
+is not saved as part of the checkpoint image: it is constructed
+dynamically during both checkpoint and restart, and discarded at the
+end of the operation.
+
+During checkpoint, when a shared object is encountered for the first
+time, it is inserted to the hash table, indexed by its kernel address.
+It is assigned an identifier (@objref) in order of appearance, and
+then its state is saved. Subsequent lookups of that object in the hash
+will yield that entry, in which case only the @objref is saved, as
+opposed the entire state of the object.
+
+During restart, shared objects are indexed by their @objref as given
+during the checkpoint. On the first appearance of each shared object,
+a new resource will be created and its state restored from the image.
+Then the object is added to the hash table. Subsequent lookups of the
+same unique identifier in the hash table will yield that entry, and
+then the existing object instance is reused instead of creating
+a new one.
+
+The hash grabs a reference to each object that is inserted, and
+maintains this reference for the entire lifetime of the hash. Thus,
+it is always safe to reference an object that is stored in the hash.
+The hash is "one-way" in the sense that objects that are added are
+never deleted from the hash until the hash is discarded. This, in
+turn, happens only when the checkpoint (or restart) terminates.
+
+Shared objects are thus saved when they are first seen, and _before_
+the parent object that uses them. Therefore by the time the parent
+objects needs them, they should already be in the objhash. The one
+exception is when more than a single shared resource will be restarted
+at once (e.g. like the two ends of a pipe, or all the namespaces in an
+nsproxy). In this case the parent object is dumped first followed by
+the individual sub-resources).
+
+The checkpoint image is stream-able, meaning that restarting from it
+may not require lseek(). This is enforced at checkpoint time, by
+carefully selecting the order of shared objects, to respect the rule
+that an object is always saved before the objects that refers to it.
+
+
+Memory contents format
+======================
+
+The memory contents of a given memory address space (->mm) is dumped
+as a sequence of vma objects, represented by 'struct ckpt_hdr_vma'.
+This header details the vma properties, and a reference to a file
+(if file backed) or an inode (or shared memory) object.
+
+The vma header is followed by the actual contents - but only those
+pages that need to be saved, i.e. dirty pages. They are written in
+chunks of data, where each chunks contains a header that indicates
+that number of pages in the chunk, followed by an array of virtual
+addresses and then an array of actual page contents. The last chunk
+holds zero pages.
+
+To illustrate this, consider a single simple task with two vmas: one
+is file mapped with two dumped pages, and the other is anonymous with
+three dumped pages. The memory dump will look like this:
+
+ ckpt_hdr + ckpt_hdr_vma
+ ckpt_hdr_pgarr (nr_pages = 2)
+ addr1, addr2
+ page1, page2
+ ckpt_hdr_pgarr (nr_pages = 0)
+ ckpt_hdr + ckpt_hdr_vma
+ ckpt_hdr_pgarr (nr_pages = 3)
+ addr3, addr4, addr5
+ page3, page4, page5
+ ckpt_hdr_pgarr (nr_pages = 0)
+
+
+Error handling
+==============
+
+Both checkpoint and restart operations may fail due to a variety of
+reasons. Using a simple, single return value from the system call is
+insufficient to report the reason of a failure.
+
+Instead, both sys_checkpoint() and sys_restart() accept an additional
+argument - a file descriptor to which the kernel writes diagnostic
+and debugging information. Both the checkpoint and restart userspace
+utilities have options to specify a filename to store this log.
+
+In addition, checkpoint provides informative status report upon
+failure in the checkpoint image in the form of (one or more) error
+objects, 'struct ckpt_hdr_err'. An error objects consists of a
+mandatory pre-header followed by a null character ('\0'), and then a
+string that describes the error. By default, if an error occurs, this
+will be the last object written to the checkpoint image.
+
+Upon failure, the caller can examine the image (e.g. with 'ckptinfo')
+and extract the detailed error message. The leading '\0' is useful if
+one wants to seek back from the end of the checkpoint image, instead
+of parsing the entire image separately.
+
+
+Security
+========
+
+The main question is whether sys_checkpoint() and sys_restart()
+require privileged or unprivileged operation.
+
+Early versions checked capable(CAP_SYS_ADMIN) assuming that we would
+attempt to remove the need for privilege, so that all users could
+safely use it. Arnd Bergmann pointed out that it'd make more sense to
+let unprivileged users use them now, so that we'll be more careful
+about the security as patches roll in.
+
+Checkpoint: the main concern is whether a task that performs the
+checkpoint of another task has sufficient privileges to access its
+state. We address this by requiring that the checkpointer task will be
+able to ptrace the target task, by means of ptrace_may_access() with
+access mode.
+
+Restart: the main concern is that we may allow an unprivileged user to
+feed the kernel with random data. To this end, the restart works in a
+way that does not skip the usual security checks. Task credentials,
+i.e. euid, reuid, and LSM security contexts currently come from the
+caller, not the checkpoint image. As credentials are restored too,
+the ability of a task that calls sys_restore() to setresuid/setresgid
+to those values must be checked.
+
+Keeping the restart procedure to operate within the limits of the
+caller's credentials means that there various scenarios that cannot
+be supported. For instance, a setuid program that opened a protected
+log file and then dropped privileges will fail the restart, because
+the user won't have enough credentials to reopen the file. In these
+cases, we should probably treat restarting like inserting a kernel
+module: surely the user can cause havoc by providing incorrect data,
+but then again we must trust the root account.
+
+So that's why we don't want CAP_SYS_ADMIN required up-front. That way
+we will be forced to more carefully review each of those features.
+However, this can be controlled with a sysctl-variable.
+
+
+Kernel interfaces
+=================
+
+* To checkpoint a vma, the 'struct vm_operations_struct' needs to
+ provide a method ->checkpoint:
+ int checkpoint(struct ckpt_ctx *, struct vma_struct *)
+ Restart requires a matching (exported) restore:
+ int restore(struct ckpt_ctx *, struct mm_struct *, struct ckpt_hdr_vma *)
+
+* To checkpoint a file, the 'struct file_operations' needs to provide
+ the methods ->checkpoint and ->collect:
+ int checkpoint(struct ckpt_ctx *, struct file *)
+ int collect(struct ckpt_ctx *, struct file *)
+ Restart requires a matching (exported) restore:
+ int restore(struct ckpt_ctx *, struct ckpt_hdr_file *)
+ For most file systems, generic_file_{checkpoint,restore}() can be
+ used.
+
+* To checkpoint a socket, the 'struct proto_ops' needs to provide
+ the methods ->checkpoint, ->collect and ->restore:
+ int checkpoint(struct ckpt_ctx *ctx, struct socket *sock);
+ int collect(struct ckpt_ctx *ctx, struct socket *sock);
+ int restore(struct ckpt_ctx *, struct socket *sock, struct ckpt_hdr_socket *h)
+
diff --git a/Documentation/checkpoint/self_checkpoint.c b/Documentation/checkpoint/self_checkpoint.c
new file mode 100644
index 0000000..27dba0d
--- /dev/null
+++ b/Documentation/checkpoint/self_checkpoint.c
@@ -0,0 +1,69 @@
+/*
+ * self_checkpoint.c: demonstrate self-checkpoint
+ *
+ * Copyright (C) 2008 Oren Laadan
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of the Linux
+ * distribution for more details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <math.h>
+#include <sys/syscall.h>
+
+#include <linux/checkpoint.h>
+
+static inline int checkpoint(pid_t pid, int fd, unsigned long flags)
+{
+ return syscall(__NR_checkpoint, pid, fd, flags, CHECKPOINT_FD_NONE);
+}
+
+#define OUTFILE "/tmp/cr-self.out"
+
+int main(int argc, char *argv[])
+{
+ pid_t pid = getpid();
+ FILE *file;
+ int i, ret;
+
+ close(0);
+ close(2);
+
+ unlink(OUTFILE);
+ file = fopen(OUTFILE, "w+");
+ if (!file) {
+ perror("open");
+ exit(1);
+ }
+ if (dup2(0, 2) < 0) {
+ perror("dup2");
+ exit(1);
+ }
+
+ fprintf(file, "hello, world!\n");
+ fflush(file);
+
+ for (i = 0; i < 1000; i++) {
+ sleep(1);
+ fprintf(file, "count %d\n", i);
+ fflush(file);
+
+ if (i != 2)
+ continue;
+ ret = checkpoint(pid, STDOUT_FILENO, CHECKPOINT_SUBTREE);
+ if (ret < 0) {
+ fprintf(file, "ckpt: %s\n", strerror(errno));
+ exit(2);
+ }
+
+ fprintf(file, "checkpoint ret: %d\n", ret);
+ fflush(file);
+ }
+
+ return 0;
+}
diff --git a/Documentation/checkpoint/self_restart.c b/Documentation/checkpoint/self_restart.c
new file mode 100644
index 0000000..647ce51
--- /dev/null
+++ b/Documentation/checkpoint/self_restart.c
@@ -0,0 +1,40 @@
+/*
+ * self_restart.c: demonstrate self-restart
+ *
+ * Copyright (C) 2008 Oren Laadan
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of the Linux
+ * distribution for more details.
+ */
+
+#define _GNU_SOURCE /* or _BSD_SOURCE or _SVID_SOURCE */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+
+#include <linux/checkpoint.h>
+
+static inline int restart(pid_t pid, int fd, unsigned long flags)
+{
+ return syscall(__NR_restart, pid, fd, flags, CHECKPOINT_FD_NONE);
+}
+
+int main(int argc, char *argv[])
+{
+ pid_t pid = getpid();
+ int ret;
+
+ ret = restart(pid, STDIN_FILENO, RESTART_TASKSELF);
+ if (ret < 0)
+ perror("restart");
+
+ printf("should not reach here !\n");
+
+ return 0;
+}
diff --git a/Documentation/checkpoint/usage.txt b/Documentation/checkpoint/usage.txt
new file mode 100644
index 0000000..c6fc045
--- /dev/null
+++ b/Documentation/checkpoint/usage.txt
@@ -0,0 +1,247 @@
+
+ How to use Checkpoint-Restart
+ =========================================
+
+
+API
+===
+
+The API consists of three new system calls:
+
+* long checkpoint(pid_t pid, int fd, unsigned long flag, int logfd);
+
+ Checkpoint a (sub-)container whose root task is identified by @pid,
+ to the open file indicated by @fd. If @logfd isn't -1, it indicates
+ an open file to which error and debug messages are written. @flags
+ may be one or more of:
+ - CHECKPOINT_SUBTREE : allow checkpoint of sub-container
+ (other value are not allowed).
+
+ Returns: a positive checkpoint identifier (ckptid) upon success, 0 if
+ it returns from a restart, and -1 if an error occurs. The ckptid will
+ uniquely identify a checkpoint image, for as long as the checkpoint
+ is kept in the kernel (e.g. if one wishes to keep a checkpoint, or a
+ partial checkpoint, residing in kernel memory).
+
+* long sys_restart(pid_t pid, int fd, unsigned long flags, int logfd);
+
+ Restart a process hierarchy from a checkpoint image that is read from
+ the blob stored in the file indicated by @fd. If @logfd isn't -1, it
+ indicates an open file to which error and debug messages are written.
+ @flags will have future meaning (must be 0 for now). @pid indicates
+ the root of the hierarchy as seen in the coordinator's pid-namespace,
+ and is expected to be a child of the coordinator. @flags may be one
+ or more of:
+ - RESTART_TASKSELF : (self) restart of a single process
+ - RESTART_FROEZN : processes remain frozen once restart completes
+ - RESTART_GHOST : process is a ghost (placeholder for a pid)
+ (Note that this argument may mean 'ckptid' to identify an in-kernel
+ checkpoint image, with some @flags in the future).
+
+ Returns: -1 if an error occurs, 0 on success when restarting from a
+ "self" checkpoint, and return value of system call at the time of the
+ checkpoint when restarting from an "external" checkpoint.
+
+ (If a process was frozen for checkpoint while in userspace, it will
+ resume running in userspace exactly where it was interrupted. If it
+ was frozen while in kernel doing a syscall, it will return what the
+ syscall returned when interrupted/completed, and proceed from there
+ as if it had only been frozen and then thawed. Finally, if it did a
+ self-checkpoint, it will resume to the first instruction after the
+ call to checkpoint(2), having returned 0, to indicate whether the
+ return is from the checkpoint or a restart).
+
+* int clone_with_pid(unsigned long clone_flags, void *news,
+ int *parent_tidptr, int *child_tidptr,
+ struct target_pid_set *pid_set)
+
+ struct target_pid_set {
+ int num_pids;
+ pid_t *target_pids;
+ }
+
+ Container restart requires that a task have the same pid it had when
+ it was checkpointed. When containers are nested the tasks within the
+ containers exist in multiple pid namespaces and hence have multiple
+ pids to specify during restart.
+
+ clone_with_pids(), intended for use during restart, is similar to
+ clone(), except that it takes a 'target_pid_set' parameter. This
+ parameter lets caller choose specific pid numbers for the child
+ process, in the process's active and ancestor pid namespaces.
+
+ Unlike clone(), clone_with_pids() needs CAP_SYS_ADMIN, at least for
+ now, to prevent unprivileged processes from misusing this interface.
+
+ If a target-pid is 0, the kernel continues to assign a pid for the
+ process in that namespace. If a requested pid is taken, the system
+ call fails with -EBUSY. If 'pid_set.num_pids' exceeds the current
+ nesting level of pid namespaces, the system call fails with -EINVAL.
+
+
+Sysctl/proc
+===========
+
+/proc/sys/kernel/ckpt_unpriv_allowed [default = 1]
+ controls whether c/r operation is allowed for unprivileged users
+
+
+Operation
+=========
+
+The granularity of a checkpoint usually is a process hierarchy. The
+'pid' argument is interpreted in the caller's pid namespace. So to
+checkpoint a container whose init task (pid 1 in that pidns) appears
+as pid 3497 the caller's pidns, the caller must use pid 3497. Passing
+pid 1 will attempt to checkpoint the caller's container, and if the
+caller isn't privileged and init is owned by root, it will fail.
+
+Unless the CHECKPOINT_SUBTREE flag is set, if the caller passes a pid
+which does not refer to a container's init task, then sys_checkpoint()
+would return -EINVAL.
+
+We assume that during checkpoint and restart the container state is
+quiescent. During checkpoint, this means that all affected tasks are
+frozen (or otherwise stopped). During restart, this means that all
+affected tasks are executing the sys_restart() call. In both cases, if
+there are other tasks possible sharing state with the container, they
+must not modify it during the operation. It is the responsibility of
+the caller to follow this requirement.
+
+If the assumption that all tasks are frozen and that there is no other
+sharing doesn't hold - then the results of the operation are undefined
+(just as, e.g. not calling execve() immediately after vfork() produces
+undefined results). In particular, either checkpoint will fail, or it
+may produce a checkpoint image that can't be restarted, or (unlikely)
+the restart may produce a container whose state does not match that of
+the original container.
+
+
+User tools
+==========
+
+* checkpoint(1): a tool to perform a checkpoint of a container/subtree
+* restart(1): a tool to restart a container/subtree
+* ckptinfo: a tool to examine a checkpoint image
+
+It is best to use the dedicated user tools for checkpoint and restart.
+
+If you insist, then here is a code snippet that illustrates how a
+checkpoint is initiated by a process inside a container - the logic is
+similar to fork():
+ ...
+ ckptid = checkpoint(0, ...);
+ switch (crid) {
+ case -1:
+ perror("checkpoint failed");
+ break;
+ default:
+ fprintf(stderr, "checkpoint succeeded, CRID=%d\n", ret);
+ /* proceed with execution after checkpoint */
+ ...
+ break;
+ case 0:
+ fprintf(stderr, "returned after restart\n");
+ /* proceed with action required following a restart */
+ ...
+ break;
+ }
+ ...
+
+And to initiate a restart, the process in an empty container can use
+logic similar to execve():
+ ...
+ if (restart(pid, ...) < 0)
+ perror("restart failed");
+ /* only get here if restart failed */
+ ...
+
+Note, that the code also supports "self" checkpoint, where a process
+can checkpoint itself. This mode does not capture the relationships of
+the task with other tasks, or any shared resources. It is useful for
+application that wish to be able to save and restore their state.
+They will either not use (or care about) shared resources, or they
+will be aware of the operations and adapt suitably after a restart.
+The code above can also be used for "self" checkpoint.
+
+
+You may find the following sample programs useful:
+
+* checkpoint.c: accepts a 'pid' and checkpoint that task to stdout
+* self_checkpoint.c: a simple test program doing self-checkpoint
+* self_restart.c: restarts a (self-) checkpoint image from stdin
+
+See also the utilities 'checkpoint' and 'restart' (from user-cr).
+
+
+"External" checkpoint
+=====================
+
+To do "external" checkpoint, you need to first freeze that other task
+either using the freezer cgroup.
+
+Restart does not preserve the original PID yet, (because we haven't
+solved yet the fork-with-specific-pid issue). In a real scenario, you
+probably want to first create a new names space, and have the init
+task there call 'sys_restart()'.
+
+I tested it this way:
+ $ ./test &
+ [1] 3493
+
+ $ echo 3493 > /cgroup/0/tasks
+ $ echo FROZEN > /cgroup/0/freezer.state
+ $ ./checkpoint 3493 > ckpt.image
+
+ $ mv /tmp/cr-test.out /tmp/cr-test.out.orig
+ $ cp /tmp/cr-test.out.orig /tmp/cr-test.out
+
+ $ echo THAWED > /cgroup/0/freezer.state
+
+ $ ./self_restart < ckpt.image
+Now compare the output of the two output files.
+
+
+"Self" checkpoint
+================
+
+To do self-checkpoint, you can incorporate the code from
+self_checkpoint.c into your application.
+
+Here is how to test the self-checkpoint:
+ $ ./self_checkpoint > self.image &
+ [1] 3512
+
+ $ sleep 3
+ $ mv /tmp/cr-self.out /tmp/cr-self.out.orig
+ $ cp /tmp/cr-self.out.orig /tmp/cr-self.out
+
+ $ cat /tmp/cr-self.out
+ hello, world!
+ count 0
+ count 1
+ count 2
+ checkpoint ret: 1
+ count 3
+ ...
+
+ $ sed -i 's/count/xxxxx/g' /tmp/cr-self.out
+
+ $ ./self_restart < self.image &
+
+Now compare the output of the two output files.
+ $ cat /tmp/cr-self.out
+ hello, world!
+ xxxxx 0
+ xxxxx 1
+ xxxxx 2
+ checkpoint ret: 0
+ count 3
+ ...
+
+
+Note how in test.c we close stdin, stdout, stderr - that's because
+currently we only support regular files (not ttys/ptys).
+
+If you check the output of ps, you'll see that "self_restart" changed
+its name to "test" or "self_checkpoint", as expected.
--
1.6.3.3
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v21 020/100] c/r: documentation
2010-05-01 14:15 ` [PATCH v21 020/100] c/r: documentation Oren Laadan
@ 2010-05-06 20:27 ` Randy Dunlap
2010-05-07 6:54 ` Oren Laadan
0 siblings, 1 reply; 16+ messages in thread
From: Randy Dunlap @ 2010-05-06 20:27 UTC (permalink / raw)
To: Oren Laadan
Cc: Andrew Morton, containers, linux-kernel, Serge Hallyn,
Matt Helsley, Pavel Emelyanov, linux-api, linux-mm, linux-fsdevel,
netdev, Dave Hansen
On Sat, 1 May 2010 10:15:02 -0400 Oren Laadan wrote:
> Covers application checkpoint/restart, overall design, interfaces,
> usage, shared objects, and and checkpoint image format.
>
> Signed-off-by: Oren Laadan <orenl@cs.columbia.edu>
> Signed-off-by: Dave Hansen <dave@linux.vnet.ibm.com>
> Acked-by: Serge E. Hallyn <serue@us.ibm.com>
> Tested-by: Serge E. Hallyn <serue@us.ibm.com>
> ---
> Documentation/checkpoint/checkpoint.c | 38 +++
> Documentation/checkpoint/readme.txt | 370 ++++++++++++++++++++++++++++
> Documentation/checkpoint/self_checkpoint.c | 69 +++++
> Documentation/checkpoint/self_restart.c | 40 +++
> Documentation/checkpoint/usage.txt | 247 +++++++++++++++++++
> 5 files changed, 764 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/checkpoint/checkpoint.c
> create mode 100644 Documentation/checkpoint/readme.txt
> create mode 100644 Documentation/checkpoint/self_checkpoint.c
> create mode 100644 Documentation/checkpoint/self_restart.c
> create mode 100644 Documentation/checkpoint/usage.txt
> diff --git a/Documentation/checkpoint/readme.txt b/Documentation/checkpoint/readme.txt
> new file mode 100644
> index 0000000..4fa5560
> --- /dev/null
> +++ b/Documentation/checkpoint/readme.txt
> @@ -0,0 +1,370 @@
> +
...
> +In contrast, when checkpointing a subtree of a container it is up to
> +the user to ensure that dependencies either don't exist or can be
> +safely ignored. This is useful, for instance, for HPC scenarios or
> +even a user that would like to periodically checkpoint a long-running
who
> +batch job.
> +
...
> +
> +Checkpoint image format
> +=======================
> +
...
> +
> +The container configuration section containers information that is
contains
> +global to the container. Security (LSM) configuration is one example.
> +Network configuration and container-wide mounts may also go here, so
> +that the userspace restart coordinator can re-create a suitable
> +environment.
> +
...
> +
> +Then the state of all tasks is saved, in the order that they appear in
> +the tasks array above. For each state, we save data like task_struct,
> +namespaces, open files, memory layout, memory contents, cpu state,
CPU (throughout, please)
> +signals and signal handlers, etc. For resources that are shared among
> +multiple processes, we first checkpoint said resource (and only once),
> +and in the task data we give a reference to it. More about shared
> +resources below.
> +
...
> +
> +Shared objects
> +==============
> +
> +Many resources may be shared by multiple tasks (e.g. file descriptors,
> +memory address space, etc), or even have multiple references from
etc.),
> +other resources (e.g. a single inode that represents two ends of a
> +pipe).
> +
...
> +Memory contents format
> +======================
> +
> +The memory contents of a given memory address space (->mm) is dumped
are (I think)
> +as a sequence of vma objects, represented by 'struct ckpt_hdr_vma'.
> +This header details the vma properties, and a reference to a file
> +(if file backed) or an inode (or shared memory) object.
> +
> +The vma header is followed by the actual contents - but only those
> +pages that need to be saved, i.e. dirty pages. They are written in
> +chunks of data, where each chunks contains a header that indicates
chunk
> +that number of pages in the chunk, followed by an array of virtual
the
> +addresses and then an array of actual page contents. The last chunk
> +holds zero pages.
> +
...
> +Kernel interfaces
> +=================
> +
> +* To checkpoint a vma, the 'struct vm_operations_struct' needs to
> + provide a method ->checkpoint:
> + int checkpoint(struct ckpt_ctx *, struct vma_struct *)
> + Restart requires a matching (exported) restore:
> + int restore(struct ckpt_ctx *, struct mm_struct *, struct ckpt_hdr_vma *)
> +
> +* To checkpoint a file, the 'struct file_operations' needs to provide
> + the methods ->checkpoint and ->collect:
> + int checkpoint(struct ckpt_ctx *, struct file *)
> + int collect(struct ckpt_ctx *, struct file *)
> + Restart requires a matching (exported) restore:
> + int restore(struct ckpt_ctx *, struct ckpt_hdr_file *)
> + For most file systems, generic_file_{checkpoint,restore}() can be
> + used.
> +
> +* To checkpoint a socket, the 'struct proto_ops' needs to provide
To checkpoint/restart a socket,
> + the methods ->checkpoint, ->collect and ->restore:
> + int checkpoint(struct ckpt_ctx *ctx, struct socket *sock);
> + int collect(struct ckpt_ctx *ctx, struct socket *sock);
> + int restore(struct ckpt_ctx *, struct socket *sock, struct ckpt_hdr_socket *h)
> diff --git a/Documentation/checkpoint/usage.txt b/Documentation/checkpoint/usage.txt
> new file mode 100644
> index 0000000..c6fc045
> --- /dev/null
> +++ b/Documentation/checkpoint/usage.txt
> @@ -0,0 +1,247 @@
> +
> + How to use Checkpoint-Restart
> + =========================================
> +
> +
> +API
> +===
> +
> +The API consists of three new system calls:
> +
> +* long checkpoint(pid_t pid, int fd, unsigned long flag, int logfd);
flags,
> +
> + Checkpoint a (sub-)container whose root task is identified by @pid,
> + to the open file indicated by @fd. If @logfd isn't -1, it indicates
> + an open file to which error and debug messages are written. @flags
> + may be one or more of:
> + - CHECKPOINT_SUBTREE : allow checkpoint of sub-container
> + (other value are not allowed).
> +
> + Returns: a positive checkpoint identifier (ckptid) upon success, 0 if
> + it returns from a restart, and -1 if an error occurs. The ckptid will
> + uniquely identify a checkpoint image, for as long as the checkpoint
> + is kept in the kernel (e.g. if one wishes to keep a checkpoint, or a
> + partial checkpoint, residing in kernel memory).
> +
> +* long sys_restart(pid_t pid, int fd, unsigned long flags, int logfd);
> +
> + Restart a process hierarchy from a checkpoint image that is read from
> + the blob stored in the file indicated by @fd. If @logfd isn't -1, it
> + indicates an open file to which error and debug messages are written.
> + @flags will have future meaning (must be 0 for now). @pid indicates
> + the root of the hierarchy as seen in the coordinator's pid-namespace,
> + and is expected to be a child of the coordinator. @flags may be one
> + or more of:
> + - RESTART_TASKSELF : (self) restart of a single process
> + - RESTART_FROEZN : processes remain frozen once restart completes
FROZEN ?
> + - RESTART_GHOST : process is a ghost (placeholder for a pid)
about @flags: Above says both of these:
a) @flags will have future meaning (must be 0 for now)
b) @flags may be one or more of:
so please decide which one it is ;)
> + (Note that this argument may mean 'ckptid' to identify an in-kernel
> + checkpoint image, with some @flags in the future).
> +
> + Returns: -1 if an error occurs, 0 on success when restarting from a
> + "self" checkpoint, and return value of system call at the time of the
> + checkpoint when restarting from an "external" checkpoint.
> +
...
> +
> +Sysctl/proc
> +===========
> +
> +/proc/sys/kernel/ckpt_unpriv_allowed [default = 1]
> + controls whether c/r operation is allowed for unprivileged users
C/R
> +
> +
> +Operation
> +=========
> +
> +The granularity of a checkpoint usually is a process hierarchy. The
> +'pid' argument is interpreted in the caller's pid namespace. So to
> +checkpoint a container whose init task (pid 1 in that pidns) appears
> +as pid 3497 the caller's pidns, the caller must use pid 3497. Passing
> +pid 1 will attempt to checkpoint the caller's container, and if the
> +caller isn't privileged and init is owned by root, it will fail.
> +
> +Unless the CHECKPOINT_SUBTREE flag is set, if the caller passes a pid
> +which does not refer to a container's init task, then sys_checkpoint()
> +would return -EINVAL.
returns -EINVAL.
...
> +
> +
> +User tools
> +==========
> +
> +* checkpoint(1): a tool to perform a checkpoint of a container/subtree
> +* restart(1): a tool to restart a container/subtree
> +* ckptinfo: a tool to examine a checkpoint image
> +
> +It is best to use the dedicated user tools for checkpoint and restart.
> +
> +If you insist, then here is a code snippet that illustrates how a
> +checkpoint is initiated by a process inside a container - the logic is
> +similar to fork():
> + ...
> + ckptid = checkpoint(0, ...);
> + switch (crid) {
(ckptid) ?
> + case -1:
> + perror("checkpoint failed");
> + break;
> + default:
> + fprintf(stderr, "checkpoint succeeded, CRID=%d\n", ret);
s/ret/ckptid/ ?
> + /* proceed with execution after checkpoint */
> + ...
> + break;
> + case 0:
> + fprintf(stderr, "returned after restart\n");
> + /* proceed with action required following a restart */
> + ...
> + break;
> + }
> + ...
> +
> +And to initiate a restart, the process in an empty container can use
> +logic similar to execve():
> + ...
> + if (restart(pid, ...) < 0)
> + perror("restart failed");
> + /* only get here if restart failed */
> + ...
> +
> +Note, that the code also supports "self" checkpoint, where a process
Note that
> +can checkpoint itself. This mode does not capture the relationships of
> +the task with other tasks, or any shared resources. It is useful for
> +application that wish to be able to save and restore their state.
applications
> +They will either not use (or care about) shared resources, or they
> +will be aware of the operations and adapt suitably after a restart.
> +The code above can also be used for "self" checkpoint.
> +
> +
> +You may find the following sample programs useful:
> +
> +* checkpoint.c: accepts a 'pid' and checkpoint that task to stdout
checkpoints
> +* self_checkpoint.c: a simple test program doing self-checkpoint
> +* self_restart.c: restarts a (self-) checkpoint image from stdin
> +
> +See also the utilities 'checkpoint' and 'restart' (from user-cr).
> +
> +
> +"External" checkpoint
> +=====================
> +
> +To do "external" checkpoint, you need to first freeze that other task
> +either using the freezer cgroup.
eh? cannot parse that.
> +
> +Restart does not preserve the original PID yet, (because we haven't
> +solved yet the fork-with-specific-pid issue). In a real scenario, you
> +probably want to first create a new names space, and have the init
namespace,
> +task there call 'sys_restart()'.
> +
> +I tested it this way:
...
---
~Randy
*** Remember to use Documentation/SubmitChecklist when testing your code ***
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v21 020/100] c/r: documentation
2010-05-06 20:27 ` Randy Dunlap
@ 2010-05-07 6:54 ` Oren Laadan
0 siblings, 0 replies; 16+ messages in thread
From: Oren Laadan @ 2010-05-07 6:54 UTC (permalink / raw)
To: Randy Dunlap
Cc: Andrew Morton, containers, linux-kernel, Serge Hallyn,
Matt Helsley, Pavel Emelyanov, linux-api, linux-mm, linux-fsdevel,
netdev, Dave Hansen
Thanks for reading carefully through and pointing out
glitches and inconsistencies. I'll fix it for next post.
Oren.
On 05/06/2010 04:27 PM, Randy Dunlap wrote:
> On Sat, 1 May 2010 10:15:02 -0400 Oren Laadan wrote:
>
>> Covers application checkpoint/restart, overall design, interfaces,
>> usage, shared objects, and and checkpoint image format.
>>
>> Signed-off-by: Oren Laadan <orenl@cs.columbia.edu>
>> Signed-off-by: Dave Hansen <dave@linux.vnet.ibm.com>
>> Acked-by: Serge E. Hallyn <serue@us.ibm.com>
>> Tested-by: Serge E. Hallyn <serue@us.ibm.com>
>> ---
>> Documentation/checkpoint/checkpoint.c | 38 +++
>> Documentation/checkpoint/readme.txt | 370 ++++++++++++++++++++++++++++
>> Documentation/checkpoint/self_checkpoint.c | 69 +++++
>> Documentation/checkpoint/self_restart.c | 40 +++
>> Documentation/checkpoint/usage.txt | 247 +++++++++++++++++++
>> 5 files changed, 764 insertions(+), 0 deletions(-)
>> create mode 100644 Documentation/checkpoint/checkpoint.c
>> create mode 100644 Documentation/checkpoint/readme.txt
>> create mode 100644 Documentation/checkpoint/self_checkpoint.c
>> create mode 100644 Documentation/checkpoint/self_restart.c
>> create mode 100644 Documentation/checkpoint/usage.txt
>
>> diff --git a/Documentation/checkpoint/readme.txt b/Documentation/checkpoint/readme.txt
>> new file mode 100644
>> index 0000000..4fa5560
>> --- /dev/null
>> +++ b/Documentation/checkpoint/readme.txt
>> @@ -0,0 +1,370 @@
>> +
> ...
>> +In contrast, when checkpointing a subtree of a container it is up to
>> +the user to ensure that dependencies either don't exist or can be
>> +safely ignored. This is useful, for instance, for HPC scenarios or
>> +even a user that would like to periodically checkpoint a long-running
>
> who
>
>> +batch job.
>> +
> ...
>
>> +
>> +Checkpoint image format
>> +=======================
>> +
> ...
>
>> +
>> +The container configuration section containers information that is
>
> contains
>
>> +global to the container. Security (LSM) configuration is one example.
>> +Network configuration and container-wide mounts may also go here, so
>> +that the userspace restart coordinator can re-create a suitable
>> +environment.
>> +
> ...
>
>> +
>> +Then the state of all tasks is saved, in the order that they appear in
>> +the tasks array above. For each state, we save data like task_struct,
>> +namespaces, open files, memory layout, memory contents, cpu state,
>
> CPU (throughout, please)
>
>> +signals and signal handlers, etc. For resources that are shared among
>> +multiple processes, we first checkpoint said resource (and only once),
>> +and in the task data we give a reference to it. More about shared
>> +resources below.
>> +
> ...
>
>> +
>> +Shared objects
>> +==============
>> +
>> +Many resources may be shared by multiple tasks (e.g. file descriptors,
>> +memory address space, etc), or even have multiple references from
>
> etc.),
>
>> +other resources (e.g. a single inode that represents two ends of a
>> +pipe).
>> +
> ...
>
>> +Memory contents format
>> +======================
>> +
>> +The memory contents of a given memory address space (->mm) is dumped
>
> are (I think)
>
>> +as a sequence of vma objects, represented by 'struct ckpt_hdr_vma'.
>> +This header details the vma properties, and a reference to a file
>> +(if file backed) or an inode (or shared memory) object.
>> +
>> +The vma header is followed by the actual contents - but only those
>> +pages that need to be saved, i.e. dirty pages. They are written in
>> +chunks of data, where each chunks contains a header that indicates
>
> chunk
>
>> +that number of pages in the chunk, followed by an array of virtual
>
> the
>
>> +addresses and then an array of actual page contents. The last chunk
>> +holds zero pages.
>> +
> ...
>
>> +Kernel interfaces
>> +=================
>> +
>> +* To checkpoint a vma, the 'struct vm_operations_struct' needs to
>> + provide a method ->checkpoint:
>> + int checkpoint(struct ckpt_ctx *, struct vma_struct *)
>> + Restart requires a matching (exported) restore:
>> + int restore(struct ckpt_ctx *, struct mm_struct *, struct ckpt_hdr_vma *)
>> +
>> +* To checkpoint a file, the 'struct file_operations' needs to provide
>> + the methods ->checkpoint and ->collect:
>> + int checkpoint(struct ckpt_ctx *, struct file *)
>> + int collect(struct ckpt_ctx *, struct file *)
>> + Restart requires a matching (exported) restore:
>> + int restore(struct ckpt_ctx *, struct ckpt_hdr_file *)
>> + For most file systems, generic_file_{checkpoint,restore}() can be
>> + used.
>> +
>> +* To checkpoint a socket, the 'struct proto_ops' needs to provide
>
> To checkpoint/restart a socket,
>
>> + the methods ->checkpoint, ->collect and ->restore:
>> + int checkpoint(struct ckpt_ctx *ctx, struct socket *sock);
>> + int collect(struct ckpt_ctx *ctx, struct socket *sock);
>> + int restore(struct ckpt_ctx *, struct socket *sock, struct ckpt_hdr_socket *h)
>
>
>> diff --git a/Documentation/checkpoint/usage.txt b/Documentation/checkpoint/usage.txt
>> new file mode 100644
>> index 0000000..c6fc045
>> --- /dev/null
>> +++ b/Documentation/checkpoint/usage.txt
>> @@ -0,0 +1,247 @@
>> +
>> + How to use Checkpoint-Restart
>> + =========================================
>> +
>> +
>> +API
>> +===
>> +
>> +The API consists of three new system calls:
>> +
>> +* long checkpoint(pid_t pid, int fd, unsigned long flag, int logfd);
>
> flags,
>
>> +
>> + Checkpoint a (sub-)container whose root task is identified by @pid,
>> + to the open file indicated by @fd. If @logfd isn't -1, it indicates
>> + an open file to which error and debug messages are written. @flags
>> + may be one or more of:
>> + - CHECKPOINT_SUBTREE : allow checkpoint of sub-container
>> + (other value are not allowed).
>> +
>> + Returns: a positive checkpoint identifier (ckptid) upon success, 0 if
>> + it returns from a restart, and -1 if an error occurs. The ckptid will
>> + uniquely identify a checkpoint image, for as long as the checkpoint
>> + is kept in the kernel (e.g. if one wishes to keep a checkpoint, or a
>> + partial checkpoint, residing in kernel memory).
>> +
>> +* long sys_restart(pid_t pid, int fd, unsigned long flags, int logfd);
>> +
>> + Restart a process hierarchy from a checkpoint image that is read from
>> + the blob stored in the file indicated by @fd. If @logfd isn't -1, it
>> + indicates an open file to which error and debug messages are written.
>> + @flags will have future meaning (must be 0 for now). @pid indicates
>> + the root of the hierarchy as seen in the coordinator's pid-namespace,
>> + and is expected to be a child of the coordinator. @flags may be one
>> + or more of:
>> + - RESTART_TASKSELF : (self) restart of a single process
>> + - RESTART_FROEZN : processes remain frozen once restart completes
>
> FROZEN ?
>
>> + - RESTART_GHOST : process is a ghost (placeholder for a pid)
>
> about @flags: Above says both of these:
> a) @flags will have future meaning (must be 0 for now)
> b) @flags may be one or more of:
>
> so please decide which one it is ;)
>
>> + (Note that this argument may mean 'ckptid' to identify an in-kernel
>> + checkpoint image, with some @flags in the future).
>> +
>> + Returns: -1 if an error occurs, 0 on success when restarting from a
>> + "self" checkpoint, and return value of system call at the time of the
>> + checkpoint when restarting from an "external" checkpoint.
>> +
> ...
>> +
>> +Sysctl/proc
>> +===========
>> +
>> +/proc/sys/kernel/ckpt_unpriv_allowed [default = 1]
>> + controls whether c/r operation is allowed for unprivileged users
>
> C/R
>
>> +
>> +
>> +Operation
>> +=========
>> +
>> +The granularity of a checkpoint usually is a process hierarchy. The
>> +'pid' argument is interpreted in the caller's pid namespace. So to
>> +checkpoint a container whose init task (pid 1 in that pidns) appears
>> +as pid 3497 the caller's pidns, the caller must use pid 3497. Passing
>> +pid 1 will attempt to checkpoint the caller's container, and if the
>> +caller isn't privileged and init is owned by root, it will fail.
>> +
>> +Unless the CHECKPOINT_SUBTREE flag is set, if the caller passes a pid
>> +which does not refer to a container's init task, then sys_checkpoint()
>> +would return -EINVAL.
>
> returns -EINVAL.
>
> ...
>
>> +
>> +
>> +User tools
>> +==========
>> +
>> +* checkpoint(1): a tool to perform a checkpoint of a container/subtree
>> +* restart(1): a tool to restart a container/subtree
>> +* ckptinfo: a tool to examine a checkpoint image
>> +
>> +It is best to use the dedicated user tools for checkpoint and restart.
>> +
>> +If you insist, then here is a code snippet that illustrates how a
>> +checkpoint is initiated by a process inside a container - the logic is
>> +similar to fork():
>> + ...
>> + ckptid = checkpoint(0, ...);
>> + switch (crid) {
>
> (ckptid) ?
>
>> + case -1:
>> + perror("checkpoint failed");
>> + break;
>> + default:
>> + fprintf(stderr, "checkpoint succeeded, CRID=%d\n", ret);
>
> s/ret/ckptid/ ?
>
>> + /* proceed with execution after checkpoint */
>> + ...
>> + break;
>> + case 0:
>> + fprintf(stderr, "returned after restart\n");
>> + /* proceed with action required following a restart */
>> + ...
>> + break;
>> + }
>> + ...
>> +
>> +And to initiate a restart, the process in an empty container can use
>> +logic similar to execve():
>> + ...
>> + if (restart(pid, ...) < 0)
>> + perror("restart failed");
>> + /* only get here if restart failed */
>> + ...
>> +
>> +Note, that the code also supports "self" checkpoint, where a process
>
> Note that
>
>> +can checkpoint itself. This mode does not capture the relationships of
>> +the task with other tasks, or any shared resources. It is useful for
>> +application that wish to be able to save and restore their state.
>
> applications
>
>> +They will either not use (or care about) shared resources, or they
>> +will be aware of the operations and adapt suitably after a restart.
>> +The code above can also be used for "self" checkpoint.
>> +
>> +
>> +You may find the following sample programs useful:
>> +
>> +* checkpoint.c: accepts a 'pid' and checkpoint that task to stdout
>
> checkpoints
>
>> +* self_checkpoint.c: a simple test program doing self-checkpoint
>> +* self_restart.c: restarts a (self-) checkpoint image from stdin
>> +
>> +See also the utilities 'checkpoint' and 'restart' (from user-cr).
>> +
>> +
>> +"External" checkpoint
>> +=====================
>> +
>> +To do "external" checkpoint, you need to first freeze that other task
>> +either using the freezer cgroup.
>
> eh? cannot parse that.
>
>> +
>> +Restart does not preserve the original PID yet, (because we haven't
>> +solved yet the fork-with-specific-pid issue). In a real scenario, you
>> +probably want to first create a new names space, and have the init
>
> namespace,
>
>> +task there call 'sys_restart()'.
>> +
>> +I tested it this way:
>
> ...
>
> ---
> ~Randy
> *** Remember to use Documentation/SubmitChecklist when testing your code ***
>
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v21 022/100] c/r: basic infrastructure for checkpoint/restart
[not found] <1272723382-19470-1-git-send-email-orenl@cs.columbia.edu>
2010-05-01 14:15 ` [PATCH v21 020/100] c/r: documentation Oren Laadan
@ 2010-05-01 14:15 ` Oren Laadan
2010-05-01 14:15 ` [PATCH v21 071/100] Add common socket helpers to unify the security hooks Oren Laadan
` (11 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Oren Laadan @ 2010-05-01 14:15 UTC (permalink / raw)
To: Andrew Morton
Cc: containers, linux-kernel, Serge Hallyn, Matt Helsley,
Pavel Emelyanov, Oren Laadan, linux-mm, linux-fsdevel, netdev
Add those interfaces, as well as helpers needed to easily manage the
file format. The code is roughly broken out as follows:
kernel/checkpoint/sys.c - user/kernel data transfer, as well as setup
of the c/r context (a per-checkpoint data structure for housekeeping)
kernel/checkpoint/checkpoint.c - output wrappers and checkpoint handling
kernel/checkpoint/restart.c - input wrappers and restart handling
kernel/checkpoint/process.c - c/r of task data
For now, we can only checkpoint the 'current' task ("self" checkpoint),
and the 'pid' argument to the syscall is ignored.
Patches to add the per-architecture support as well as the actual
work to do the memory checkpoint follow in subsequent patches.
Changelog[v21]:
- Complain if checkpoint_hdr.h included without CONFIG_CHECKPOINT
- Do not include checkpoint_hdr.h explicitly
- Consolidate ckpt_read/write with kernel_read/write
- Reorganize code:move checkpoint/* to kernel/checkpoint/*
- [Christoffer Dall] Fix trivial bug in ckpt_msg macro
Changelog[v20]:
- Export key symbols to enable c/r from kernel modules
Changelog[v19]:
- [Serge Hallyn] Use ckpt_err() to for bad header values
Changelog[v19-rc3]:
- sys_{checkpoint,restart} to use ptregs prototype
Changelog[v19-rc1]:
- Set ctx->errno in do_ckpt_msg() if needed
- Document prototype of ckpt_write_err in header
- Update prototype of ckpt_read_obj()
- Fix up headers so we can munge them for use by userspace
- [Matt Helsley] Check for empty string for _ckpt_write_err()
- [Matt Helsley] Add cpp definitions for enums
- [Serge Hallyn] Add global section container to image format
- [Matt Helsley] Fix total byte read/write count for large images
- ckpt_read_buf_type() to accept max payload (excludes ckpt_hdr)
- [Serge Hallyn] Define new api for error and debug logging
- Use logfd in sys_{checkpoint,restart}
Changelog[v18]:
- Detect error-headers in input data on restart, and abort.
- Standard format for checkpoint error strings (and documentation)
- [Matt Helsley] Rename headerless struct ckpt_hdr_* to struct ckpt_*
- [Dan Smith] Add an errno validation function
- Add ckpt_read_payload(): read a variable-length object (no header)
- Add ckpt_read_string(): same for strings (ensures null-terminated)
- Add ckpt_read_consume(): consumes next object without processing
Changelog[v17]:
- Fix compilation for architectures that don't support checkpoint
- Save/restore t->{set,clear}_child_tid
- Restart(2) isn't idempotent: must return -EINTR if interrupted
- ckpt_debug does not depend on DYNAMIC_DEBUG, on by default
- Export generic checkpoint headers to userespace
- Fix comment for prototype of sys_restart
- Have ckpt_debug() print global-pid and __LINE__
- Only save and test kernel constants once (in header)
Changelog[v16]:
- Split ctx->flags to ->uflags (user flags) and ->kflags (kernel flags)
- Introduce __ckpt_write_err() and ckpt_write_err() to report errors
- Allow @ptr == NULL to write (or read) header only without payload
- Introduce _ckpt_read_obj_type()
Changelog[v15]:
- Replace header buffer in ckpt_ctx (hbuf,hpos) with kmalloc/kfree()
Changelog[v14]:
- Cleanup interface to get/put hdr buffers
- Merge checkpoint and restart code into a single file (per subsystem)
- Take uts_sem around access to uts->{release,version,machine}
- Embed ckpt_hdr in all ckpt_hdr_...., cleanup read/write helpers
- Define sys_checkpoint(0,...) as asking for a self-checkpoint (Serge)
- Revert use of 'pr_fmt' to avoid tainting whom includes us (Nathan Lynch)
- Explicitly indicate length of UTS fields in header
- Discard field 'h->parent' from ckpt_hdr
Changelog[v12]:
- ckpt_kwrite/ckpt_kread() again use vfs_read(), vfs_write() (safer)
- Split ckpt_write/ckpt_read() to two parts: _ckpt_write/read() helper
- Befriend with sparse : explicit conversion to 'void __user *'
- Redfine 'pr_fmt' instead of using special ckpt_debug()
Changelog[v10]:
- add ckpt_write_buffer(), ckpt_read_buffer() and ckpt_read_buf_type()
- force end-of-string in ckpt_read_string() (fix possible DoS)
Changelog[v9]:
- ckpt_kwrite/ckpt_kread() use file->f_op->write() directly
- Drop ckpt_uwrite/ckpt_uread() since they aren't used anywhere
Changelog[v6]:
- Balance all calls to ckpt_hbuf_get() with matching ckpt_hbuf_put()
(although it's not really needed)
Changelog[v5]:
- Rename headers files s/ckpt/checkpoint/
Changelog[v2]:
- Added utsname->{release,version,machine} to checkpoint header
- Pad header structures to 64 bits to ensure compatibility
Cc: linux-mm@kvack.org
Cc: linux-fsdevel@vger.kernel.org
Cc: netdev@vger.kernel.org
Signed-off-by: Oren Laadan <orenl@cs.columbia.edu>
Acked-by: Serge E. Hallyn <serue@us.ibm.com>
Tested-by: Serge E. Hallyn <serue@us.ibm.com>
---
arch/x86/include/asm/unistd_32.h | 2 -
arch/x86/kernel/syscall_table_32.S | 2 -
include/linux/Kbuild | 3 +
include/linux/checkpoint.h | 202 ++++++++++++++++
include/linux/checkpoint_hdr.h | 135 +++++++++++
include/linux/checkpoint_types.h | 44 ++++
include/linux/magic.h | 3 +
include/linux/syscalls.h | 4 -
kernel/checkpoint/Makefile | 6 +-
kernel/checkpoint/checkpoint.c | 213 +++++++++++++++++
kernel/checkpoint/process.c | 101 ++++++++
kernel/checkpoint/restart.c | 460 +++++++++++++++++++++++++++++++++++
kernel/checkpoint/sys.c | 461 +++++++++++++++++++++++++++++++++++-
lib/Kconfig.debug | 13 +
14 files changed, 1632 insertions(+), 17 deletions(-)
create mode 100644 include/linux/checkpoint.h
create mode 100644 include/linux/checkpoint_hdr.h
create mode 100644 include/linux/checkpoint_types.h
create mode 100644 kernel/checkpoint/checkpoint.c
create mode 100644 kernel/checkpoint/process.c
create mode 100644 kernel/checkpoint/restart.c
diff --git a/arch/x86/include/asm/unistd_32.h b/arch/x86/include/asm/unistd_32.h
index 007d7cd..cb67842 100644
--- a/arch/x86/include/asm/unistd_32.h
+++ b/arch/x86/include/asm/unistd_32.h
@@ -344,8 +344,6 @@
#define __NR_perf_event_open 336
#define __NR_recvmmsg 337
#define __NR_eclone 338
-#define __NR_checkpoint 339
-#define __NR_restart 340
#ifdef __KERNEL__
diff --git a/arch/x86/kernel/syscall_table_32.S b/arch/x86/kernel/syscall_table_32.S
index 2d5a6b0..0c92570 100644
--- a/arch/x86/kernel/syscall_table_32.S
+++ b/arch/x86/kernel/syscall_table_32.S
@@ -338,5 +338,3 @@ ENTRY(sys_call_table)
.long sys_perf_event_open
.long sys_recvmmsg
.long ptregs_eclone
- .long sys_checkpoint
- .long sys_restart /* 340 */
diff --git a/include/linux/Kbuild b/include/linux/Kbuild
index e2ea0b2..71bb8d1 100644
--- a/include/linux/Kbuild
+++ b/include/linux/Kbuild
@@ -45,6 +45,9 @@ header-y += bsg.h
header-y += can.h
header-y += cciss_defs.h
header-y += cdk.h
+header-y += checkpoint.h
+header-y += checkpoint_hdr.h
+header-y += checkpoint_types.h
header-y += chio.h
header-y += coda_psdev.h
header-y += coff.h
diff --git a/include/linux/checkpoint.h b/include/linux/checkpoint.h
new file mode 100644
index 0000000..4bb5b8d
--- /dev/null
+++ b/include/linux/checkpoint.h
@@ -0,0 +1,202 @@
+#ifndef _LINUX_CHECKPOINT_H_
+#define _LINUX_CHECKPOINT_H_
+/*
+ * Generic checkpoint-restart
+ *
+ * Copyright (C) 2008-2009 Oren Laadan
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of the Linux
+ * distribution for more details.
+ */
+
+#define CHECKPOINT_VERSION 3
+
+/* misc user visible */
+#define CHECKPOINT_FD_NONE -1
+
+#ifdef __KERNEL__
+#ifdef CONFIG_CHECKPOINT
+
+#include <linux/checkpoint_types.h>
+#include <linux/checkpoint_hdr.h>
+#include <linux/err.h>
+
+/* sycall helpers */
+extern long do_sys_checkpoint(pid_t pid, int fd,
+ unsigned long flags, int logfd);
+extern long do_sys_restart(pid_t pid, int fd,
+ unsigned long flags, int logfd);
+
+/* ckpt_ctx: kflags */
+#define CKPT_CTX_CHECKPOINT_BIT 0
+#define CKPT_CTX_RESTART_BIT 1
+#define CKPT_CTX_ERROR_BIT 3
+
+#define CKPT_CTX_CHECKPOINT (1 << CKPT_CTX_CHECKPOINT_BIT)
+#define CKPT_CTX_RESTART (1 << CKPT_CTX_RESTART_BIT)
+#define CKPT_CTX_ERROR (1 << CKPT_CTX_ERROR_BIT)
+
+
+extern int ckpt_kwrite(struct ckpt_ctx *ctx, void *buf, size_t count);
+extern int ckpt_kread(struct ckpt_ctx *ctx, void *buf, size_t count);
+
+extern void _ckpt_hdr_put(struct ckpt_ctx *ctx, void *ptr, int n);
+extern void ckpt_hdr_put(struct ckpt_ctx *ctx, void *ptr);
+extern void *ckpt_hdr_get(struct ckpt_ctx *ctx, int n);
+extern void *ckpt_hdr_get_type(struct ckpt_ctx *ctx, int n, int type);
+
+extern int ckpt_write_obj(struct ckpt_ctx *ctx, struct ckpt_hdr *h);
+extern int ckpt_write_obj_type(struct ckpt_ctx *ctx,
+ void *ptr, int len, int type);
+extern int ckpt_write_buffer(struct ckpt_ctx *ctx, void *ptr, int len);
+extern int ckpt_write_string(struct ckpt_ctx *ctx, char *str, int len);
+
+extern int _ckpt_read_obj_type(struct ckpt_ctx *ctx,
+ void *ptr, int len, int type);
+extern int _ckpt_read_buffer(struct ckpt_ctx *ctx, void *ptr, int len);
+extern int _ckpt_read_string(struct ckpt_ctx *ctx, void *ptr, int len);
+extern void *ckpt_read_obj_type(struct ckpt_ctx *ctx, int len, int type);
+extern void *ckpt_read_buf_type(struct ckpt_ctx *ctx, int max, int type);
+extern int ckpt_read_payload(struct ckpt_ctx *ctx,
+ void **ptr, int max, int type);
+extern char *ckpt_read_string(struct ckpt_ctx *ctx, int max);
+extern int ckpt_read_consume(struct ckpt_ctx *ctx, int len, int type);
+
+extern long do_checkpoint(struct ckpt_ctx *ctx, pid_t pid);
+extern long do_restart(struct ckpt_ctx *ctx, pid_t pid);
+
+/* task */
+extern int checkpoint_task(struct ckpt_ctx *ctx, struct task_struct *t);
+extern int restore_task(struct ckpt_ctx *ctx);
+
+static inline int ckpt_validate_errno(int errno)
+{
+ return (errno >= 0) && (errno < MAX_ERRNO);
+}
+
+/* debugging flags */
+#define CKPT_DBASE 0x1 /* anything */
+#define CKPT_DSYS 0x2 /* generic (system) */
+#define CKPT_DRW 0x4 /* image read/write */
+
+#define CKPT_DDEFAULT 0xffff /* default debug level */
+
+#ifndef CKPT_DFLAG
+#define CKPT_DFLAG 0xffff /* everything */
+#endif
+
+#ifdef CONFIG_CHECKPOINT_DEBUG
+extern unsigned long ckpt_debug_level;
+
+/*
+ * This is deprecated
+ */
+/* use this to select a specific debug level */
+#define _ckpt_debug(level, fmt, args...) \
+ do { \
+ if (ckpt_debug_level & (level)) \
+ printk(KERN_DEBUG "[%d:%d:c/r:%s:%d] " fmt, \
+ current->pid, \
+ current->nsproxy ? \
+ task_pid_vnr(current) : -1, \
+ __func__, __LINE__, ## args); \
+ } while (0)
+
+/*
+ * CKPT_DBASE is the base flags, doesn't change
+ * CKPT_DFLAG is to be redfined in each source file
+ */
+#define ckpt_debug(fmt, args...) \
+ _ckpt_debug(CKPT_DBASE | CKPT_DFLAG, fmt, ## args)
+
+#else
+
+/*
+ * This is deprecated
+ */
+#define _ckpt_debug(level, fmt, args...) do { } while (0)
+#define ckpt_debug(fmt, args...) do { } while (0)
+
+#endif /* CONFIG_CHECKPOINT_DEBUG */
+
+/*
+ * prototypes for the new logging api
+ */
+
+extern void ckpt_msg_lock(struct ckpt_ctx *ctx);
+extern void ckpt_msg_unlock(struct ckpt_ctx *ctx);
+
+extern void _do_ckpt_msg(struct ckpt_ctx *ctx, int err, char *fmt, ...);
+extern void do_ckpt_msg(struct ckpt_ctx *ctx, int err, char *fmt, ...);
+
+/*
+ * Append formatted msg to ctx->msg[ctx->msg_len].
+ * Must be called after expanding format.
+ * May be called under spinlock.
+ * Must be called under ckpt_msg_lock().
+ */
+extern void _ckpt_msg_append(struct ckpt_ctx *ctx, char *fmt, ...);
+
+/*
+ * Write ctx->msg to all relevant places.
+ * Must not be called under spinlock.
+ * Must be called under ckpt_msg_lock().
+ */
+extern void _ckpt_msg_complete(struct ckpt_ctx *ctx);
+
+/*
+ * Append an enhanced formatted message to ctx->msg.
+ * This will not write the message out to the applicable files, so
+ * the caller will have to use _ckpt_msg_complete() to finish up.
+ * @ctx must be a valid checkpoint context.
+ * @fmt is the extended format
+ *
+ * Must be called with ckpt_msg_lock held.
+ */
+#define _ckpt_msg(ctx, fmt, args...) do { \
+ _do_ckpt_msg(ctx, 0, fmt, ##args); \
+} while (0)
+
+/*
+ * Append an enhanced formatted message to ctx->msg.
+ * This will take the ckpt_msg_lock and also write the message out
+ * to the applicable files by calling _ckpt_msg_complete().
+ * @ctx must be a valid checkpoint context.
+ * @fmt is the extended format
+ *
+ * Must not be called under spinlock.
+ */
+#define ckpt_msg(ctx, fmt, args...) do { \
+ do_ckpt_msg(ctx, 0, fmt, ##args); \
+} while (0)
+
+/*
+ * Report an error.
+ * This will take the ckpt_msg_lock and also write the message out
+ * to the applicable files by calling _ckpt_msg_complete().
+ * @ctx must be a valid checkpoint context.
+ * @err is the error value
+ * @fmt is the extended format
+ *
+ * Must not be called under spinlock.
+ */
+
+#define ckpt_err(ctx, err, fmt, args...) do { \
+ do_ckpt_msg(ctx, err, "[E @ %s:%d]" fmt, __func__, __LINE__, ##args); \
+} while (0)
+
+/*
+ * Same as ckpt_err() but
+ * must be called with ctx->msg_mutex held
+ * can be called under spinlock
+ * must be followed by a call to _ckpt_msg_complete()
+ */
+#define _ckpt_err(ctx, err, fmt, args...) do { \
+ _do_ckpt_msg(ctx, err, "[E @ %s:%d]" fmt, __func__, __LINE__, ##args); \
+} while (0)
+
+#endif /* CONFIG_CHECKPOINT */
+#endif /* __KERNEL__ */
+
+#endif /* _LINUX_CHECKPOINT_H_ */
diff --git a/include/linux/checkpoint_hdr.h b/include/linux/checkpoint_hdr.h
new file mode 100644
index 0000000..7ccebc7
--- /dev/null
+++ b/include/linux/checkpoint_hdr.h
@@ -0,0 +1,135 @@
+#ifndef _CHECKPOINT_CKPT_HDR_H_
+#define _CHECKPOINT_CKPT_HDR_H_
+/*
+ * Generic container checkpoint-restart
+ *
+ * Copyright (C) 2008-2010 Oren Laadan
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of the Linux
+ * distribution for more details.
+ */
+
+#ifndef __KERNEL__
+#include <sys/types.h>
+#include <linux/types.h>
+#endif
+
+#ifdef __KERNEL__
+#include <linux/types.h>
+
+#ifndef CONFIG_CHECKPOINT
+#error linux/checkpoint_hdr.h included directly (without CONFIG_CHECKPOINT)
+#endif
+
+#endif
+
+#include <linux/utsname.h>
+
+/*
+ * To maintain compatibility between 32-bit and 64-bit architecture flavors,
+ * keep data 64-bit aligned: use padding for structure members, and use
+ * __attribute__((aligned (8))) for the entire structure.
+ *
+ * Quoting Arnd Bergmann:
+ * "This structure has an odd multiple of 32-bit members, which means
+ * that if you put it into a larger structure that also contains 64-bit
+ * members, the larger structure may get different alignment on x86-32
+ * and x86-64, which you might want to avoid. I can't tell if this is
+ * an actual problem here. ... In this case, I'm pretty sure that
+ * sizeof(ckpt_hdr_task) on x86-32 is different from x86-64, since it
+ * will be 32-bit aligned on x86-32."
+ */
+
+/*
+ * header format: 'struct ckpt_hdr' must prefix all other headers. Therfore
+ * when a header is passed around, the information about it (type, size)
+ * is readily available. Structs that include a struct ckpt_hdr are named
+ * struct ckpt_hdr_* by convention (usualy the struct ckpt_hdr is the first
+ * member).
+ */
+struct ckpt_hdr {
+ __u32 type;
+ __u32 len;
+} __attribute__((aligned(8)));
+
+/* header types */
+enum {
+ CKPT_HDR_HEADER = 1,
+#define CKPT_HDR_HEADER CKPT_HDR_HEADER
+ CKPT_HDR_CONTAINER,
+#define CKPT_HDR_CONTAINER CKPT_HDR_CONTAINER
+ CKPT_HDR_BUFFER,
+#define CKPT_HDR_BUFFER CKPT_HDR_BUFFER
+ CKPT_HDR_STRING,
+#define CKPT_HDR_STRING CKPT_HDR_STRING
+
+ CKPT_HDR_TASK = 101,
+#define CKPT_HDR_TASK CKPT_HDR_TASK
+
+ CKPT_HDR_TAIL = 9001,
+#define CKPT_HDR_TAIL CKPT_HDR_TAIL
+
+ CKPT_HDR_ERROR = 9999,
+#define CKPT_HDR_ERROR CKPT_HDR_ERROR
+};
+
+/* kernel constants */
+struct ckpt_const {
+ /* task */
+ __u16 task_comm_len;
+ /* uts */
+ __u16 uts_release_len;
+ __u16 uts_version_len;
+ __u16 uts_machine_len;
+} __attribute__((aligned(8)));
+
+/* checkpoint image header */
+struct ckpt_hdr_header {
+ struct ckpt_hdr h;
+ __u64 magic;
+
+ __u16 _padding;
+
+ __u16 major;
+ __u16 minor;
+ __u16 patch;
+ __u16 rev;
+
+ struct ckpt_const constants;
+
+ __u64 time; /* when checkpoint taken */
+ __u64 uflags; /* uflags from checkpoint */
+
+ /*
+ * the header is followed by three strings:
+ * char release[const.uts_release_len];
+ * char version[const.uts_version_len];
+ * char machine[const.uts_machine_len];
+ */
+} __attribute__((aligned(8)));
+
+/* checkpoint image trailer */
+struct ckpt_hdr_tail {
+ struct ckpt_hdr h;
+ __u64 magic;
+} __attribute__((aligned(8)));
+
+/* container configuration section header */
+struct ckpt_hdr_container {
+ struct ckpt_hdr h;
+} __attribute__((aligned(8)));;
+
+/* task data */
+struct ckpt_hdr_task {
+ struct ckpt_hdr h;
+ __u32 state;
+ __u32 exit_state;
+ __u32 exit_code;
+ __u32 exit_signal;
+
+ __u64 set_child_tid;
+ __u64 clear_child_tid;
+} __attribute__((aligned(8)));
+
+#endif /* _CHECKPOINT_CKPT_HDR_H_ */
diff --git a/include/linux/checkpoint_types.h b/include/linux/checkpoint_types.h
new file mode 100644
index 0000000..13d6dd5
--- /dev/null
+++ b/include/linux/checkpoint_types.h
@@ -0,0 +1,44 @@
+#ifndef _LINUX_CHECKPOINT_TYPES_H_
+#define _LINUX_CHECKPOINT_TYPES_H_
+/*
+ * Generic checkpoint-restart
+ *
+ * Copyright (C) 2008-2009 Oren Laadan
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of the Linux
+ * distribution for more details.
+ */
+
+#ifdef __KERNEL__
+
+#include <linux/fs.h>
+
+struct ckpt_ctx {
+ int crid; /* unique checkpoint id */
+
+ pid_t root_pid; /* container identifier */
+
+ unsigned long kflags; /* kerenl flags */
+ unsigned long uflags; /* user flags */
+ unsigned long oflags; /* restart: uflags from checkpoint */
+
+ struct file *file; /* input/output file */
+ struct file *logfile; /* status/debug log file */
+ loff_t total; /* total read/written */
+
+ struct task_struct *tsk;/* checkpoint: current target task */
+ char err_string[256]; /* checkpoint: error string */
+
+ int errno; /* errno that caused failure */
+
+#define CKPT_MSG_LEN 1024
+ char fmt[CKPT_MSG_LEN];
+ char msg[CKPT_MSG_LEN];
+ int msglen;
+ struct mutex msg_mutex;
+};
+
+#endif /* __KERNEL__ */
+
+#endif /* _LINUX_CHECKPOINT_TYPES_H_ */
diff --git a/include/linux/magic.h b/include/linux/magic.h
index eb9800f..e04117a 100644
--- a/include/linux/magic.h
+++ b/include/linux/magic.h
@@ -58,4 +58,7 @@
#define DEVPTS_SUPER_MAGIC 0x1cd1
#define SOCKFS_MAGIC 0x534F434B
+#define CHECKPOINT_MAGIC_HEAD 0x00feed0cc0a2d200LL
+#define CHECKPOINT_MAGIC_TAIL 0x002d2a0cc0deef00LL
+
#endif /* __LINUX_MAGIC_H__ */
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index d1d1703..057929b 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -834,10 +834,6 @@ asmlinkage long sys_pselect6(int, fd_set __user *, fd_set __user *,
asmlinkage long sys_ppoll(struct pollfd __user *, unsigned int,
struct timespec __user *, const sigset_t __user *,
size_t);
-asmlinkage long sys_checkpoint(pid_t pid, int fd, unsigned long flags,
- int logfd);
-asmlinkage long sys_restart(pid_t pid, int fd, unsigned long flags,
- int logfd);
int kernel_execve(const char *filename, char *const argv[], char *const envp[]);
diff --git a/kernel/checkpoint/Makefile b/kernel/checkpoint/Makefile
index 8a32c6f..99364cc 100644
--- a/kernel/checkpoint/Makefile
+++ b/kernel/checkpoint/Makefile
@@ -2,4 +2,8 @@
# Makefile for linux checkpoint/restart.
#
-obj-$(CONFIG_CHECKPOINT) += sys.o
+obj-$(CONFIG_CHECKPOINT) += \
+ sys.o \
+ checkpoint.o \
+ restart.o \
+ process.o
diff --git a/kernel/checkpoint/checkpoint.c b/kernel/checkpoint/checkpoint.c
new file mode 100644
index 0000000..75b43e6
--- /dev/null
+++ b/kernel/checkpoint/checkpoint.c
@@ -0,0 +1,213 @@
+/*
+ * Checkpoint logic and helpers
+ *
+ * Copyright (C) 2008-2009 Oren Laadan
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of the Linux
+ * distribution for more details.
+ */
+
+/* default debug level for output */
+#define CKPT_DFLAG CKPT_DSYS
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/time.h>
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/dcache.h>
+#include <linux/mount.h>
+#include <linux/utsname.h>
+#include <linux/magic.h>
+#include <linux/checkpoint.h>
+
+/* unique checkpoint identifier (FIXME: should be per-container ?) */
+static atomic_t ctx_count = ATOMIC_INIT(0);
+
+/**
+ * ckpt_write_obj - write an object
+ * @ctx: checkpoint context
+ * @h: object descriptor
+ */
+int ckpt_write_obj(struct ckpt_ctx *ctx, struct ckpt_hdr *h)
+{
+ _ckpt_debug(CKPT_DRW, "type %d len %d\n", h->type, h->len);
+ return ckpt_kwrite(ctx, h, h->len);
+}
+EXPORT_SYMBOL(ckpt_write_obj);
+
+/**
+ * ckpt_write_obj_type - write an object (from a pointer)
+ * @ctx: checkpoint context
+ * @ptr: buffer pointer
+ * @len: buffer size
+ * @type: desired type
+ *
+ * If @ptr is NULL, then write only the header (payload to follow)
+ */
+int ckpt_write_obj_type(struct ckpt_ctx *ctx, void *ptr, int len, int type)
+{
+ struct ckpt_hdr *h;
+ int ret;
+
+ h = ckpt_hdr_get(ctx, sizeof(*h));
+ if (!h)
+ return -ENOMEM;
+
+ h->type = type;
+ h->len = len + sizeof(*h);
+
+ _ckpt_debug(CKPT_DRW, "type %d len %d\n", h->type, h->len);
+ ret = ckpt_kwrite(ctx, h, sizeof(*h));
+ if (ret < 0)
+ goto out;
+ if (ptr)
+ ret = ckpt_kwrite(ctx, ptr, len);
+ out:
+ _ckpt_hdr_put(ctx, h, sizeof(*h));
+ return ret;
+}
+EXPORT_SYMBOL(ckpt_write_obj_type);
+
+/**
+ * ckpt_write_buffer - write an object of type buffer
+ * @ctx: checkpoint context
+ * @ptr: buffer pointer
+ * @len: buffer size
+ */
+int ckpt_write_buffer(struct ckpt_ctx *ctx, void *ptr, int len)
+{
+ return ckpt_write_obj_type(ctx, ptr, len, CKPT_HDR_BUFFER);
+}
+EXPORT_SYMBOL(ckpt_write_buffer);
+
+/**
+ * ckpt_write_string - write an object of type string
+ * @ctx: checkpoint context
+ * @str: string pointer
+ * @len: string length
+ */
+int ckpt_write_string(struct ckpt_ctx *ctx, char *str, int len)
+{
+ return ckpt_write_obj_type(ctx, str, len, CKPT_HDR_STRING);
+}
+EXPORT_SYMBOL(ckpt_write_string);
+
+/***********************************************************************
+ * Checkpoint
+ */
+
+static void fill_kernel_const(struct ckpt_const *h)
+{
+ struct task_struct *tsk;
+ struct new_utsname *uts;
+
+ /* task */
+ h->task_comm_len = sizeof(tsk->comm);
+ /* uts */
+ h->uts_release_len = sizeof(uts->release);
+ h->uts_version_len = sizeof(uts->version);
+ h->uts_machine_len = sizeof(uts->machine);
+}
+
+/* write the checkpoint header */
+static int checkpoint_write_header(struct ckpt_ctx *ctx)
+{
+ struct ckpt_hdr_header *h;
+ struct new_utsname *uts;
+ struct timeval ktv;
+ int ret;
+
+ h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_HEADER);
+ if (!h)
+ return -ENOMEM;
+
+ do_gettimeofday(&ktv);
+ uts = utsname();
+
+ h->magic = CHECKPOINT_MAGIC_HEAD;
+ h->major = (LINUX_VERSION_CODE >> 16) & 0xff;
+ h->minor = (LINUX_VERSION_CODE >> 8) & 0xff;
+ h->patch = (LINUX_VERSION_CODE) & 0xff;
+
+ h->rev = CHECKPOINT_VERSION;
+
+ h->uflags = ctx->uflags;
+ h->time = ktv.tv_sec;
+
+ fill_kernel_const(&h->constants);
+
+ ret = ckpt_write_obj(ctx, &h->h);
+ ckpt_hdr_put(ctx, h);
+ if (ret < 0)
+ return ret;
+
+ down_read(&uts_sem);
+ ret = ckpt_write_buffer(ctx, uts->release, sizeof(uts->release));
+ if (ret < 0)
+ goto up;
+ ret = ckpt_write_buffer(ctx, uts->version, sizeof(uts->version));
+ if (ret < 0)
+ goto up;
+ ret = ckpt_write_buffer(ctx, uts->machine, sizeof(uts->machine));
+ up:
+ up_read(&uts_sem);
+ return ret;
+}
+
+/* write the container configuration section */
+static int checkpoint_container(struct ckpt_ctx *ctx)
+{
+ struct ckpt_hdr_container *h;
+ int ret;
+
+ h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_CONTAINER);
+ if (!h)
+ return -ENOMEM;
+ ret = ckpt_write_obj(ctx, &h->h);
+ ckpt_hdr_put(ctx, h);
+
+ return ret;
+}
+
+/* write the checkpoint trailer */
+static int checkpoint_write_tail(struct ckpt_ctx *ctx)
+{
+ struct ckpt_hdr_tail *h;
+ int ret;
+
+ h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_TAIL);
+ if (!h)
+ return -ENOMEM;
+
+ h->magic = CHECKPOINT_MAGIC_TAIL;
+
+ ret = ckpt_write_obj(ctx, &h->h);
+ ckpt_hdr_put(ctx, h);
+ return ret;
+}
+
+long do_checkpoint(struct ckpt_ctx *ctx, pid_t pid)
+{
+ long ret;
+
+ ret = checkpoint_write_header(ctx);
+ if (ret < 0)
+ goto out;
+ ret = checkpoint_container(ctx);
+ if (ret < 0)
+ goto out;
+ ret = checkpoint_task(ctx, current);
+ if (ret < 0)
+ goto out;
+ ret = checkpoint_write_tail(ctx);
+ if (ret < 0)
+ goto out;
+
+ /* on success, return (unique) checkpoint identifier */
+ ctx->crid = atomic_inc_return(&ctx_count);
+ ret = ctx->crid;
+ out:
+ return ret;
+}
diff --git a/kernel/checkpoint/process.c b/kernel/checkpoint/process.c
new file mode 100644
index 0000000..abd9025
--- /dev/null
+++ b/kernel/checkpoint/process.c
@@ -0,0 +1,101 @@
+/*
+ * Checkpoint task structure
+ *
+ * Copyright (C) 2008-2009 Oren Laadan
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of the Linux
+ * distribution for more details.
+ */
+
+/* default debug level for output */
+#define CKPT_DFLAG CKPT_DSYS
+
+#include <linux/sched.h>
+#include <linux/checkpoint.h>
+
+/***********************************************************************
+ * Checkpoint
+ */
+
+/* dump the task_struct of a given task */
+static int checkpoint_task_struct(struct ckpt_ctx *ctx, struct task_struct *t)
+{
+ struct ckpt_hdr_task *h;
+ int ret;
+
+ h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_TASK);
+ if (!h)
+ return -ENOMEM;
+
+ h->state = t->state;
+ h->exit_state = t->exit_state;
+ h->exit_code = t->exit_code;
+ h->exit_signal = t->exit_signal;
+
+ h->set_child_tid = (unsigned long) t->set_child_tid;
+ h->clear_child_tid = (unsigned long) t->clear_child_tid;
+
+ /* FIXME: save remaining relevant task_struct fields */
+
+ ret = ckpt_write_obj(ctx, &h->h);
+ ckpt_hdr_put(ctx, h);
+ if (ret < 0)
+ return ret;
+
+ return ckpt_write_string(ctx, t->comm, TASK_COMM_LEN);
+}
+
+/* dump the entire state of a given task */
+int checkpoint_task(struct ckpt_ctx *ctx, struct task_struct *t)
+{
+ int ret;
+
+ ctx->tsk = t;
+
+ ret = checkpoint_task_struct(ctx, t);
+ ckpt_debug("task %d\n", ret);
+
+ ctx->tsk = NULL;
+ return ret;
+}
+
+/***********************************************************************
+ * Restart
+ */
+
+/* read the task_struct into the current task */
+static int restore_task_struct(struct ckpt_ctx *ctx)
+{
+ struct ckpt_hdr_task *h;
+ struct task_struct *t = current;
+ int ret;
+
+ h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_TASK);
+ if (IS_ERR(h))
+ return PTR_ERR(h);
+
+ memset(t->comm, 0, TASK_COMM_LEN);
+ ret = _ckpt_read_string(ctx, t->comm, TASK_COMM_LEN);
+ if (ret < 0)
+ goto out;
+
+ t->set_child_tid = (int __user *) (unsigned long) h->set_child_tid;
+ t->clear_child_tid = (int __user *) (unsigned long) h->clear_child_tid;
+
+ /* FIXME: restore remaining relevant task_struct fields */
+ out:
+ ckpt_hdr_put(ctx, h);
+ return ret;
+}
+
+/* read the entire state of the current task */
+int restore_task(struct ckpt_ctx *ctx)
+{
+ int ret;
+
+ ret = restore_task_struct(ctx);
+ ckpt_debug("task %d\n", ret);
+
+ return ret;
+}
diff --git a/kernel/checkpoint/restart.c b/kernel/checkpoint/restart.c
new file mode 100644
index 0000000..cd9945c
--- /dev/null
+++ b/kernel/checkpoint/restart.c
@@ -0,0 +1,460 @@
+/*
+ * Restart logic and helpers
+ *
+ * Copyright (C) 2008-2009 Oren Laadan
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of the Linux
+ * distribution for more details.
+ */
+
+/* default debug level for output */
+#define CKPT_DFLAG CKPT_DSYS
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/file.h>
+#include <linux/magic.h>
+#include <linux/utsname.h>
+#include <linux/checkpoint.h>
+
+static int _ckpt_read_err(struct ckpt_ctx *ctx, struct ckpt_hdr *h)
+{
+ char *ptr;
+ int len, ret;
+
+ len = h->len - sizeof(*h);
+ ptr = kzalloc(len + 1, GFP_KERNEL);
+ if (!ptr) {
+ ckpt_debug("insufficient memory to report image error\n");
+ return -ENOMEM;
+ }
+
+ ret = ckpt_kread(ctx, ptr, len);
+ if (ret >= 0) {
+ ckpt_debug("%s\n", &ptr[1]);
+ ret = -EIO;
+ }
+
+ kfree(ptr);
+ return ret;
+}
+
+/**
+ * _ckpt_read_obj - read an object (ckpt_hdr followed by payload)
+ * @ctx: checkpoint context
+ * @h: desired ckpt_hdr
+ * @ptr: desired buffer
+ * @len: desired object length (if 0, flexible)
+ * @max: maximum object length (if 0, flexible)
+ *
+ * If @ptr is NULL, then read only the header (payload to follow)
+ */
+static int _ckpt_read_obj(struct ckpt_ctx *ctx, struct ckpt_hdr *h,
+ void *ptr, int len, int max)
+{
+ int ret;
+
+ again:
+ ret = ckpt_kread(ctx, h, sizeof(*h));
+ if (ret < 0)
+ return ret;
+ _ckpt_debug(CKPT_DRW, "type %d len %d(%d,%d)\n",
+ h->type, h->len, len, max);
+ if (h->len < sizeof(*h))
+ return -EINVAL;
+
+ if (h->type == CKPT_HDR_ERROR) {
+ ret = _ckpt_read_err(ctx, h);
+ if (ret < 0)
+ return ret;
+ goto again;
+ }
+
+ /* if len specified, enforce, else if maximum specified, enforce */
+ if ((len && h->len != len) || (!len && max && h->len > max))
+ return -EINVAL;
+
+ if (ptr)
+ ret = ckpt_kread(ctx, ptr, h->len - sizeof(struct ckpt_hdr));
+ return ret;
+}
+
+/**
+ * _ckpt_read_obj_type - read an object of some type
+ * @ctx: checkpoint context
+ * @ptr: provided buffer
+ * @len: buffer length
+ * @type: buffer type
+ *
+ * If @ptr is NULL, then read only the header (payload to follow).
+ * @len specifies the expected buffer length (ignored if set to 0).
+ * Returns: actual _payload_ length
+ */
+int _ckpt_read_obj_type(struct ckpt_ctx *ctx, void *ptr, int len, int type)
+{
+ struct ckpt_hdr h;
+ int ret;
+
+ if (len)
+ len += sizeof(struct ckpt_hdr);
+ ret = _ckpt_read_obj(ctx, &h, ptr, len, len);
+ if (ret < 0)
+ return ret;
+ if (h.type != type)
+ return -EINVAL;
+ return h.len - sizeof(h);
+}
+EXPORT_SYMBOL(_ckpt_read_obj_type);
+
+/**
+ * _ckpt_read_buffer - read an object of type buffer (set length)
+ * @ctx: checkpoint context
+ * @ptr: provided buffer
+ * @len: buffer length
+ *
+ * If @ptr is NULL, then read only the header (payload to follow).
+ * @len specifies the expected buffer length (ignored if set to 0).
+ * Returns: _payload_ length.
+ */
+int _ckpt_read_buffer(struct ckpt_ctx *ctx, void *ptr, int len)
+{
+ BUG_ON(!len);
+ return _ckpt_read_obj_type(ctx, ptr, len, CKPT_HDR_BUFFER);
+}
+EXPORT_SYMBOL(_ckpt_read_buffer);
+
+/**
+ * _ckpt_read_string - read an object of type string (set length)
+ * @ctx: checkpoint context
+ * @ptr: provided buffer
+ * @len: string length (including '\0')
+ *
+ * If @ptr is NULL, then read only the header (payload to follow)
+ */
+int _ckpt_read_string(struct ckpt_ctx *ctx, void *ptr, int len)
+{
+ int ret;
+
+ BUG_ON(!len);
+ ret = _ckpt_read_obj_type(ctx, ptr, len, CKPT_HDR_STRING);
+ if (ret < 0)
+ return ret;
+ if (ptr)
+ ((char *) ptr)[len - 1] = '\0'; /* always play it safe */
+ return 0;
+}
+EXPORT_SYMBOL(_ckpt_read_string);
+
+/**
+ * ckpt_read_obj - allocate and read an object (ckpt_hdr followed by payload)
+ * @ctx: checkpoint context
+ * @h: object descriptor
+ * @len: desired total length (if 0, flexible)
+ * @max: maximum total length
+ *
+ * Return: new buffer allocated on success, error pointer otherwise
+ */
+static void *ckpt_read_obj(struct ckpt_ctx *ctx, int len, int max)
+{
+ struct ckpt_hdr hh;
+ struct ckpt_hdr *h;
+ int ret;
+
+ ret = ckpt_kread(ctx, &hh, sizeof(hh));
+ if (ret < 0)
+ return ERR_PTR(ret);
+ _ckpt_debug(CKPT_DRW, "type %d len %d(%d,%d)\n",
+ hh.type, hh.len, len, max);
+ if (hh.len < sizeof(*h))
+ return ERR_PTR(-EINVAL);
+ /* if len specified, enforce, else if maximum specified, enforce */
+ if ((len && hh.len != len) || (!len && max && hh.len > max))
+ return ERR_PTR(-EINVAL);
+
+ h = ckpt_hdr_get(ctx, hh.len);
+ if (!h)
+ return ERR_PTR(-ENOMEM);
+
+ *h = hh; /* yay ! */
+
+ ret = ckpt_kread(ctx, (h + 1), hh.len - sizeof(struct ckpt_hdr));
+ if (ret < 0) {
+ ckpt_hdr_put(ctx, h);
+ h = ERR_PTR(ret);
+ }
+
+ return h;
+}
+
+/**
+ * ckpt_read_obj_type - allocate and read an object of some type
+ * @ctx: checkpoint context
+ * @len: desired object length
+ * @type: desired object type
+ *
+ * Return: new buffer allocated on success, error pointer otherwise
+ */
+void *ckpt_read_obj_type(struct ckpt_ctx *ctx, int len, int type)
+{
+ struct ckpt_hdr *h;
+
+ BUG_ON(!len);
+
+ h = ckpt_read_obj(ctx, len, len);
+ if (IS_ERR(h))
+ return h;
+
+ if (h->type != type) {
+ ckpt_hdr_put(ctx, h);
+ h = ERR_PTR(-EINVAL);
+ }
+
+ return h;
+}
+EXPORT_SYMBOL(ckpt_read_obj_type);
+
+/**
+ * ckpt_read_buf_type - allocate and read an object of some type (flxible)
+ * @ctx: checkpoint context
+ * @max: maximum payload length
+ * @type: desired object type
+ *
+ * This differs from ckpt_read_obj_type() in that the length of the
+ * incoming object is flexible (up to the maximum specified by @max;
+ * unlimited if @max is 0), as determined by the ckpt_hdr data.
+ *
+ * NOTE: for symmetry with checkpoint, @max is the maximum _payload_
+ * size, excluding the header.
+ *
+ * Return: new buffer allocated on success, error pointer otherwise
+ */
+void *ckpt_read_buf_type(struct ckpt_ctx *ctx, int max, int type)
+{
+ struct ckpt_hdr *h;
+
+ if (max)
+ max += sizeof(struct ckpt_hdr);
+
+ h = ckpt_read_obj(ctx, 0, max);
+ if (IS_ERR(h))
+ return h;
+
+ if (h->type != type) {
+ ckpt_hdr_put(ctx, h);
+ h = ERR_PTR(-EINVAL);
+ }
+
+ return h;
+}
+EXPORT_SYMBOL(ckpt_read_buf_type);
+
+/**
+ * ckpt_read_payload - allocate and read the payload of an object
+ * @ctx: checkpoint context
+ * @max: maximum payload length
+ * @str: pointer to buffer to be allocated (caller must free)
+ * @type: desired object type
+ *
+ * This can be used to read a variable-length _payload_ from the checkpoint
+ * stream. @max limits the size of the resulting buffer.
+ *
+ * Return: actual _payload_ length
+ */
+int ckpt_read_payload(struct ckpt_ctx *ctx, void **ptr, int max, int type)
+{
+ int len, ret;
+
+ len = _ckpt_read_obj_type(ctx, NULL, 0, type);
+ if (len < 0)
+ return len;
+ else if (len > max)
+ return -EINVAL;
+
+ *ptr = kmalloc(len, GFP_KERNEL);
+ if (!*ptr)
+ return -ENOMEM;
+
+ ret = ckpt_kread(ctx, *ptr, len);
+ if (ret < 0) {
+ kfree(*ptr);
+ return ret;
+ }
+
+ return len;
+}
+EXPORT_SYMBOL(ckpt_read_payload);
+
+/**
+ * ckpt_read_string - allocate and read a string (variable length)
+ * @ctx: checkpoint context
+ * @max: maximum acceptable length
+ *
+ * Return: allocate string or error pointer
+ */
+char *ckpt_read_string(struct ckpt_ctx *ctx, int max)
+{
+ char *str;
+ int len;
+
+ len = ckpt_read_payload(ctx, (void **)&str, max, CKPT_HDR_STRING);
+ if (len < 0)
+ return ERR_PTR(len);
+ str[len - 1] = '\0'; /* always play it safe */
+ return str;
+}
+EXPORT_SYMBOL(ckpt_read_string);
+
+/**
+ * ckpt_read_consume - consume the next object of expected type
+ * @ctx: checkpoint context
+ * @len: desired object length
+ * @type: desired object type
+ *
+ * This can be used to skip an object in the input stream when the
+ * data is unnecessary for the restart. @len indicates the length of
+ * the object); if @len is zero the length is unconstrained.
+ */
+int ckpt_read_consume(struct ckpt_ctx *ctx, int len, int type)
+{
+ struct ckpt_hdr *h;
+ int ret = 0;
+
+ h = ckpt_read_obj(ctx, len, 0);
+ if (IS_ERR(h))
+ return PTR_ERR(h);
+
+ if (h->type != type)
+ ret = -EINVAL;
+
+ ckpt_hdr_put(ctx, h);
+ return ret;
+}
+EXPORT_SYMBOL(ckpt_read_consume);
+
+/***********************************************************************
+ * Restart
+ */
+
+static int check_kernel_const(struct ckpt_const *h)
+{
+ struct task_struct *tsk;
+ struct new_utsname *uts;
+
+ /* task */
+ if (h->task_comm_len != sizeof(tsk->comm))
+ return -EINVAL;
+ /* uts */
+ if (h->uts_release_len != sizeof(uts->release))
+ return -EINVAL;
+ if (h->uts_version_len != sizeof(uts->version))
+ return -EINVAL;
+ if (h->uts_machine_len != sizeof(uts->machine))
+ return -EINVAL;
+
+ return 0;
+}
+
+/* read the checkpoint header */
+static int restore_read_header(struct ckpt_ctx *ctx)
+{
+ struct ckpt_hdr_header *h;
+ struct new_utsname *uts = NULL;
+ int ret;
+
+ h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_HEADER);
+ if (IS_ERR(h))
+ return PTR_ERR(h);
+
+ ret = -EINVAL;
+ if (h->magic != CHECKPOINT_MAGIC_HEAD ||
+ h->rev != CHECKPOINT_VERSION ||
+ h->major != ((LINUX_VERSION_CODE >> 16) & 0xff) ||
+ h->minor != ((LINUX_VERSION_CODE >> 8) & 0xff) ||
+ h->patch != ((LINUX_VERSION_CODE) & 0xff)) {
+ ckpt_err(ctx, ret, "incompatible kernel version");
+ goto out;
+ }
+ if (h->uflags) {
+ ckpt_err(ctx, ret, "incompatible restart user flags");
+ goto out;
+ }
+
+ ret = check_kernel_const(&h->constants);
+ if (ret < 0) {
+ ckpt_err(ctx, ret, "incompatible kernel constants");
+ goto out;
+ }
+
+ ret = -ENOMEM;
+ uts = kmalloc(sizeof(*uts), GFP_KERNEL);
+ if (!uts)
+ goto out;
+
+ ctx->oflags = h->uflags;
+
+ /* FIX: verify compatibility of release, version and machine */
+ ret = _ckpt_read_buffer(ctx, uts->release, sizeof(uts->release));
+ if (ret < 0)
+ goto out;
+ ret = _ckpt_read_buffer(ctx, uts->version, sizeof(uts->version));
+ if (ret < 0)
+ goto out;
+ ret = _ckpt_read_buffer(ctx, uts->machine, sizeof(uts->machine));
+ out:
+ kfree(uts);
+ ckpt_hdr_put(ctx, h);
+ return ret;
+}
+
+/* read the container configuration section */
+static int restore_container(struct ckpt_ctx *ctx)
+{
+ int ret = 0;
+ struct ckpt_hdr_container *h;
+
+ h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_CONTAINER);
+ if (IS_ERR(h))
+ return PTR_ERR(h);
+ ckpt_hdr_put(ctx, h);
+
+ return ret;
+}
+
+/* read the checkpoint trailer */
+static int restore_read_tail(struct ckpt_ctx *ctx)
+{
+ struct ckpt_hdr_tail *h;
+ int ret = 0;
+
+ h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_TAIL);
+ if (IS_ERR(h))
+ return PTR_ERR(h);
+
+ if (h->magic != CHECKPOINT_MAGIC_TAIL)
+ ret = -EINVAL;
+
+ ckpt_hdr_put(ctx, h);
+ return ret;
+}
+
+long do_restart(struct ckpt_ctx *ctx, pid_t pid)
+{
+ long ret;
+
+ ret = restore_read_header(ctx);
+ if (ret < 0)
+ return ret;
+ ret = restore_container(ctx);
+ if (ret < 0)
+ return ret;
+ ret = restore_task(ctx);
+ if (ret < 0)
+ return ret;
+ ret = restore_read_tail(ctx);
+
+ /* on success, adjust the return value if needed [TODO] */
+ return ret;
+}
diff --git a/kernel/checkpoint/sys.c b/kernel/checkpoint/sys.c
index a81750a..af8c1bf 100644
--- a/kernel/checkpoint/sys.c
+++ b/kernel/checkpoint/sys.c
@@ -8,12 +8,398 @@
* distribution for more details.
*/
+/* default debug level for output */
+#define CKPT_DFLAG CKPT_DSYS
+
#include <linux/sched.h>
+#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/syscalls.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/uaccess.h>
+#include <linux/capability.h>
+#include <linux/checkpoint.h>
+
+/*
+ * Helpers to write(read) from(to) kernel space to(from) the checkpoint
+ * image file descriptor (similar to how a core-dump is performed).
+ *
+ * _ckpt_kwrite() - write a kernel-space buffer to a file
+ * _ckpt_kread() - read from a file to a kernel-space buffer
+ *
+ * ckpt_kread() - read from the checkpoint image to a kernel-space buffer
+ * ckpt_kwrite() - write a kernel-space buffer to the checkpoint image
+ *
+ * They latter two succeed only if the entire read or write succeeds,
+ * and return 0, or negative error otherwise.
+ */
+
+static ssize_t _ckpt_kwrite(struct file *file, void *addr, size_t count)
+{
+ loff_t pos;
+ int ret;
+
+ pos = file_pos_read(file);
+ ret = kernel_write(file, pos, addr, count);
+ if (ret < 0)
+ return ret;
+ file_pos_write(file, pos + ret);
+ return ret;
+}
+
+/* returns 0 on success */
+int ckpt_kwrite(struct ckpt_ctx *ctx, void *addr, size_t count)
+{
+ int ret;
+
+ ret = _ckpt_kwrite(ctx->file, addr, count);
+ if (ret < 0)
+ return ret;
+
+ ctx->total += count;
+ return 0;
+}
+
+static ssize_t _ckpt_kread(struct file *file, void *addr, size_t count)
+{
+ loff_t pos;
+ int ret;
+
+ pos = file_pos_read(file);
+ ret = kernel_read(file, pos, addr, count);
+ if (ret < 0)
+ return ret;
+ file_pos_write(file, pos + ret);
+ return ret;
+}
+
+/* returns 0 on success */
+int ckpt_kread(struct ckpt_ctx *ctx, void *addr, size_t count)
+{
+ int ret;
+
+ ret = _ckpt_kread(ctx->file, addr, count);
+ if (ret < 0)
+ return ret;
+ if (ret != count)
+ return -EPIPE;
+
+ ctx->total += count;
+ return 0;
+}
+
+/**
+ * ckpt_hdr_get - get a hdr of certain size
+ * @ctx: checkpoint context
+ * @len: desired length
+ *
+ * Returns pointer to header
+ */
+void *ckpt_hdr_get(struct ckpt_ctx *ctx, int len)
+{
+ return kzalloc(len, GFP_KERNEL);
+}
+EXPORT_SYMBOL(ckpt_hdr_get);
+
+/**
+ * _ckpt_hdr_put - free a hdr allocated with ckpt_hdr_get
+ * @ctx: checkpoint context
+ * @ptr: header to free
+ * @len: header length
+ *
+ * (requiring 'ptr' makes it easily interchangable with kmalloc/kfree
+ */
+void _ckpt_hdr_put(struct ckpt_ctx *ctx, void *ptr, int len)
+{
+ kfree(ptr);
+}
+EXPORT_SYMBOL(_ckpt_hdr_put);
+
+/**
+ * ckpt_hdr_put - free a hdr allocated with ckpt_hdr_get
+ * @ctx: checkpoint context
+ * @ptr: header to free
+ *
+ * It is assumed that @ptr begins with a 'struct ckpt_hdr'.
+ */
+void ckpt_hdr_put(struct ckpt_ctx *ctx, void *ptr)
+{
+ struct ckpt_hdr *h = (struct ckpt_hdr *) ptr;
+ _ckpt_hdr_put(ctx, ptr, h->len);
+}
+EXPORT_SYMBOL(ckpt_hdr_put);
+
+/**
+ * ckpt_hdr_get_type - get a hdr of certain size
+ * @ctx: checkpoint context
+ * @len: number of bytes to reserve
+ *
+ * Returns pointer to reserved space on hbuf
+ */
+void *ckpt_hdr_get_type(struct ckpt_ctx *ctx, int len, int type)
+{
+ struct ckpt_hdr *h;
+
+ h = ckpt_hdr_get(ctx, len);
+ if (!h)
+ return NULL;
+
+ h->type = type;
+ h->len = len;
+ return h;
+}
+EXPORT_SYMBOL(ckpt_hdr_get_type);
+
+/*
+ * Helpers to manage c/r contexts: allocated for each checkpoint and/or
+ * restart operation, and persists until the operation is completed.
+ */
+
+static void ckpt_ctx_free(struct ckpt_ctx *ctx)
+{
+ if (ctx->file)
+ fput(ctx->file);
+ if (ctx->logfile)
+ fput(ctx->logfile);
+ kfree(ctx);
+}
+
+static struct ckpt_ctx *ckpt_ctx_alloc(int fd, unsigned long uflags,
+ unsigned long kflags, int logfd)
+{
+ struct ckpt_ctx *ctx;
+ int err;
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return ERR_PTR(-ENOMEM);
+
+ ctx->uflags = uflags;
+ ctx->kflags = kflags;
+
+ mutex_init(&ctx->msg_mutex);
+
+ err = -EBADF;
+ ctx->file = fget(fd);
+ if (!ctx->file)
+ goto err;
+ if (logfd == CHECKPOINT_FD_NONE)
+ goto nolog;
+ ctx->logfile = fget(logfd);
+ if (!ctx->logfile)
+ goto err;
+ nolog:
+ return ctx;
+ err:
+ ckpt_ctx_free(ctx);
+ return ERR_PTR(err);
+}
+
+static void ckpt_set_error(struct ckpt_ctx *ctx, int err)
+{
+ ctx->errno = err;
+}
+
+/* helpers to handler log/dbg/err messages */
+void ckpt_msg_lock(struct ckpt_ctx *ctx)
+{
+ if (!ctx)
+ return;
+ mutex_lock(&ctx->msg_mutex);
+ ctx->msg[0] = '\0';
+ ctx->msglen = 1;
+}
+
+void ckpt_msg_unlock(struct ckpt_ctx *ctx)
+{
+ if (!ctx)
+ return;
+ mutex_unlock(&ctx->msg_mutex);
+}
+
+static inline int is_special_flag(char *s)
+{
+ if (*s == '%' && s[1] == '(' && s[2] != '\0' && s[3] == ')')
+ return 1;
+ return 0;
+}
+
+/*
+ * _ckpt_generate_fmt - handle the special flags in the enhanced format
+ * strings used by checkpoint/restart error messages.
+ * @ctx: checkpoint context
+ * @fmt: message format
+ *
+ * The special flags are surrounded by %() to help them visually stand
+ * out. For instance, %(O) means an objref. The following special
+ * flags are recognized:
+ * O: objref
+ * P: pointer
+ * T: task
+ * S: string
+ * V: variable
+ *
+ * %(O) will be expanded to "[obj %d]". Likewise P, S, and V, will
+ * also expand to format flags requiring an argument to the subsequent
+ * sprintf or printk. T will be expanded to a string with no flags,
+ * requiring no further arguments.
+ *
+ * These do not accept any extra flags (i.e. min field width, precision,
+ * etc).
+ *
+ * The caller of ckpt_err() and _ckpt_err() must provide
+ * the additional variabes, in order, to match the @fmt (except for
+ * the T key), e.g.:
+ *
+ * ckpt_err(ctx, err, "%(T)FILE flags %d %(O)\n", flags, objref);
+ *
+ * May be called under spinlock.
+ * Must be called with ctx->msg_mutex held. The expanded format
+ * will be placed in ctx->fmt.
+ */
+static void _ckpt_generate_fmt(struct ckpt_ctx *ctx, char *fmt)
+{
+ char *s = ctx->fmt;
+ int len = 0;
+
+ for (; *fmt && len < CKPT_MSG_LEN; fmt++) {
+ if (!is_special_flag(fmt)) {
+ s[len++] = *fmt;
+ continue;
+ }
+ switch (fmt[2]) {
+ case 'O':
+ len += snprintf(s+len, CKPT_MSG_LEN-len, "[obj %%d]");
+ break;
+ case 'P':
+ len += snprintf(s+len, CKPT_MSG_LEN-len, "[ptr %%p]");
+ break;
+ case 'V':
+ len += snprintf(s+len, CKPT_MSG_LEN-len, "[sym %%pS]");
+ break;
+ case 'S':
+ len += snprintf(s+len, CKPT_MSG_LEN-len, "[str %%s]");
+ break;
+ case 'T':
+ if (ctx->tsk)
+ len += snprintf(s+len, CKPT_MSG_LEN-len,
+ "[pid %d tsk %s]",
+ task_pid_vnr(ctx->tsk), ctx->tsk->comm);
+ else
+ len += snprintf(s+len, CKPT_MSG_LEN-len,
+ "[pid -1 tsk NULL]");
+ break;
+ default:
+ printk(KERN_ERR "c/r: bad format specifier %c\n",
+ fmt[2]);
+ BUG();
+ }
+ fmt += 3;
+ }
+ if (len == CKPT_MSG_LEN)
+ s[CKPT_MSG_LEN-1] = '\0';
+ else
+ s[len] = '\0';
+}
+
+static void _ckpt_msg_appendv(struct ckpt_ctx *ctx, int err, char *fmt,
+ va_list ap)
+{
+ int len = ctx->msglen;
+
+ if (err) {
+ len += snprintf(&ctx->msg[len], CKPT_MSG_LEN-len, "[err %d]",
+ err);
+ if (len > CKPT_MSG_LEN)
+ goto full;
+ }
+
+ len += snprintf(&ctx->msg[len], CKPT_MSG_LEN-len, "[pos %lld]",
+ ctx->total);
+ len += vsnprintf(&ctx->msg[len], CKPT_MSG_LEN-len, fmt, ap);
+ if (len > CKPT_MSG_LEN) {
+full:
+ len = CKPT_MSG_LEN;
+ ctx->msg[CKPT_MSG_LEN-1] = '\0';
+ }
+ ctx->msglen = len;
+}
+
+void _ckpt_msg_append(struct ckpt_ctx *ctx, char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ _ckpt_msg_appendv(ctx, 0, fmt, ap);
+ va_end(ap);
+}
+
+void _ckpt_msg_complete(struct ckpt_ctx *ctx)
+{
+ int ret;
+
+ /* Don't write an empty or uninitialized msg */
+ if (ctx->msglen <= 1)
+ return;
+
+ if (ctx->kflags & CKPT_CTX_CHECKPOINT && ctx->errno) {
+ ret = ckpt_write_obj_type(ctx, NULL, 0, CKPT_HDR_ERROR);
+ if (!ret)
+ ret = ckpt_write_string(ctx, ctx->msg, ctx->msglen);
+ if (ret < 0)
+ printk(KERN_NOTICE "c/r: error string unsaved (%d): %s\n",
+ ret, ctx->msg+1);
+ }
+
+ if (ctx->logfile) {
+ struct file *logfile = ctx->logfile;
+ loff_t pos = file_pos_read(logfile);
+ ret = kernel_write(logfile, pos, ctx->msg+1, ctx->msglen-1);
+ if (ret > 0)
+ file_pos_write(logfile, pos + ret);
+ }
+
+#ifdef CONFIG_CHECKPOINT_DEBUG
+ printk(KERN_DEBUG "%s", ctx->msg+1);
+#endif
+
+ ctx->msglen = 0;
+}
+
+#define __do_ckpt_msg(ctx, err, fmt) do { \
+ va_list ap; \
+ _ckpt_generate_fmt(ctx, fmt); \
+ va_start(ap, fmt); \
+ _ckpt_msg_appendv(ctx, err, ctx->fmt, ap); \
+ va_end(ap); \
+} while (0)
+
+void _do_ckpt_msg(struct ckpt_ctx *ctx, int err, char *fmt, ...)
+{
+ __do_ckpt_msg(ctx, err, fmt);
+}
+
+void do_ckpt_msg(struct ckpt_ctx *ctx, int err, char *fmt, ...)
+{
+ if (!ctx)
+ return;
+
+ ckpt_msg_lock(ctx);
+ __do_ckpt_msg(ctx, err, fmt);
+ _ckpt_msg_complete(ctx);
+ ckpt_msg_unlock(ctx);
+
+ if (err)
+ ckpt_set_error(ctx, err);
+}
+EXPORT_SYMBOL(do_ckpt_msg);
+
+/* checkpoint/restart syscalls */
/**
- * sys_checkpoint - checkpoint a container
+ * do_sys_checkpoint - checkpoint a container
* @pid: pid of the container init(1) process
* @fd: file to which dump the checkpoint image
* @flags: checkpoint operation flags
@@ -22,14 +408,32 @@
* Returns positive identifier on success, 0 when returning from restart
* or negative value on error
*/
-SYSCALL_DEFINE4(checkpoint, pid_t, pid, int, fd,
- unsigned long, flags, int, logfd)
+long do_sys_checkpoint(pid_t pid, int fd, unsigned long flags, int logfd)
{
- return -ENOSYS;
+ struct ckpt_ctx *ctx;
+ long ret;
+
+ /* no flags for now */
+ if (flags)
+ return -EINVAL;
+
+ if (pid == 0)
+ pid = task_pid_vnr(current);
+ ctx = ckpt_ctx_alloc(fd, flags, CKPT_CTX_CHECKPOINT, logfd);
+ if (IS_ERR(ctx))
+ return PTR_ERR(ctx);
+
+ ret = do_checkpoint(ctx, pid);
+
+ if (!ret)
+ ret = ctx->crid;
+
+ ckpt_ctx_free(ctx);
+ return ret;
}
/**
- * sys_restart - restart a container
+ * do_sys_restart - restart a container
* @pid: pid of task root (in coordinator's namespace), or 0
* @fd: file from which read the checkpoint image
* @flags: restart operation flags
@@ -38,8 +442,49 @@ SYSCALL_DEFINE4(checkpoint, pid_t, pid, int, fd,
* Returns negative value on error, or otherwise returns in the realm
* of the original checkpoint
*/
-SYSCALL_DEFINE4(restart, pid_t, pid, int, fd,
- unsigned long, flags, int, logfd)
+long do_sys_restart(pid_t pid, int fd, unsigned long flags, int logfd)
+{
+ struct ckpt_ctx *ctx = NULL;
+ long ret;
+
+ /* no flags for now */
+ if (flags)
+ return -EINVAL;
+
+ ctx = ckpt_ctx_alloc(fd, flags, CKPT_CTX_RESTART, logfd);
+ if (IS_ERR(ctx))
+ return PTR_ERR(ctx);
+
+ ret = do_restart(ctx, pid);
+
+ /* restart(2) isn't idempotent: can't restart syscall */
+ if (ret == -ERESTARTSYS || ret == -ERESTARTNOINTR ||
+ ret == -ERESTARTNOHAND || ret == -ERESTART_RESTARTBLOCK)
+ ret = -EINTR;
+
+ ckpt_ctx_free(ctx);
+ return ret;
+}
+
+
+/* 'ckpt_debug_level' controls the verbosity level of c/r code */
+#ifdef CONFIG_CHECKPOINT_DEBUG
+
+/* FIX: allow to change during runtime */
+unsigned long __read_mostly ckpt_debug_level = CKPT_DDEFAULT;
+EXPORT_SYMBOL(ckpt_debug_level);
+
+static __init int ckpt_debug_setup(char *s)
{
- return -ENOSYS;
+ long val, ret;
+
+ ret = strict_strtoul(s, 10, &val);
+ if (ret < 0)
+ return ret;
+ ckpt_debug_level = val;
+ return 0;
}
+
+__setup("ckpt_debug=", ckpt_debug_setup);
+
+#endif /* CONFIG_CHECKPOINT_DEBUG */
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 935248b..75d413e 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1086,6 +1086,19 @@ config DMA_API_DEBUG
This option causes a performance degredation. Use only if you want
to debug device drivers. If unsure, say N.
+config CHECKPOINT_DEBUG
+ bool "Checkpoint/restart debugging (EXPERIMENTAL)"
+ depends on CHECKPOINT
+ default y
+ help
+ This options turns on the debugging output of checkpoint/restart.
+ The level of verbosity is controlled by 'ckpt_debug_level' and can
+ be set at boot time with "ckpt_debug=" option.
+
+ Turning this option off will reduce the size of the c/r code. If
+ turned on, it is unlikely to incur visible overhead if the debug
+ level is set to zero.
+
source "samples/Kconfig"
source "lib/Kconfig.kgdb"
--
1.6.3.3
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v21 071/100] Add common socket helpers to unify the security hooks
[not found] <1272723382-19470-1-git-send-email-orenl@cs.columbia.edu>
2010-05-01 14:15 ` [PATCH v21 020/100] c/r: documentation Oren Laadan
2010-05-01 14:15 ` [PATCH v21 022/100] c/r: basic infrastructure for checkpoint/restart Oren Laadan
@ 2010-05-01 14:15 ` Oren Laadan
2010-05-01 14:15 ` [PATCH v21 072/100] c/r: introduce checkpoint/restore methods to struct proto_ops Oren Laadan
` (10 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Oren Laadan @ 2010-05-01 14:15 UTC (permalink / raw)
To: Andrew Morton
Cc: containers, linux-kernel, Serge Hallyn, Matt Helsley,
Pavel Emelyanov, Dan Smith, netdev
From: Dan Smith <danms@us.ibm.com>
This moves the meat out of the bind(), getsockname(), and getpeername() syscalls
into helper functions that performs security_socket_bind() and then the
sock->ops->call(). This allows a unification of this behavior between the
syscalls and the pending socket restart logic.
Cc: netdev@vger.kernel.org
Signed-off-by: Dan Smith <danms@us.ibm.com>
Acked-by: Serge E. Hallyn <serue@us.ibm.com>
Tested-by: Serge E. Hallyn <serue@us.ibm.com>
---
include/net/sock.h | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
net/socket.c | 29 ++++++-----------------------
2 files changed, 54 insertions(+), 23 deletions(-)
diff --git a/include/net/sock.h b/include/net/sock.h
index b4603cd..3cf7de4 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h
@@ -1645,6 +1645,54 @@ extern void sock_enable_timestamp(struct sock *sk, int flag);
extern int sock_get_timestamp(struct sock *, struct timeval __user *);
extern int sock_get_timestampns(struct sock *, struct timespec __user *);
+/* bind() helper shared between any callers needing to perform a bind on
+ * behalf of userspace (syscall and restart) with the security hooks.
+ */
+static inline int sock_bind(struct socket *sock,
+ struct sockaddr *addr,
+ int addr_len)
+{
+ int err;
+
+ err = security_socket_bind(sock, addr, addr_len);
+ if (err)
+ return err;
+ else
+ return sock->ops->bind(sock, addr, addr_len);
+}
+
+/* getname() helper shared between any callers needing to perform a getname on
+ * behalf of userspace (syscall and restart) with the security hooks.
+ */
+static inline int sock_getname(struct socket *sock,
+ struct sockaddr *addr,
+ int *addr_len)
+{
+ int err;
+
+ err = security_socket_getsockname(sock);
+ if (err)
+ return err;
+ else
+ return sock->ops->getname(sock, addr, addr_len, 0);
+}
+
+/* getpeer() helper shared between any callers needing to perform a getpeer on
+ * behalf of userspace (syscall and restart) with the security hooks.
+ */
+static inline int sock_getpeer(struct socket *sock,
+ struct sockaddr *addr,
+ int *addr_len)
+{
+ int err;
+
+ err = security_socket_getpeername(sock);
+ if (err)
+ return err;
+ else
+ return sock->ops->getname(sock, addr, addr_len, 1);
+}
+
/*
* Enable debug/info messages
*/
diff --git a/net/socket.c b/net/socket.c
index 5e8d0af..b9f421b 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -1422,15 +1422,10 @@ SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
err = move_addr_to_kernel(umyaddr, addrlen, (struct sockaddr *)&address);
- if (err >= 0) {
- err = security_socket_bind(sock,
- (struct sockaddr *)&address,
- addrlen);
- if (!err)
- err = sock->ops->bind(sock,
- (struct sockaddr *)
- &address, addrlen);
- }
+ if (err >= 0)
+ err = sock_bind(sock,
+ (struct sockaddr *)&address,
+ addrlen);
fput_light(sock->file, fput_needed);
}
return err;
@@ -1609,11 +1604,7 @@ SYSCALL_DEFINE3(getsockname, int, fd, struct sockaddr __user *, usockaddr,
if (!sock)
goto out;
- err = security_socket_getsockname(sock);
- if (err)
- goto out_put;
-
- err = sock->ops->getname(sock, (struct sockaddr *)&address, &len, 0);
+ err = sock_getname(sock, (struct sockaddr *)&address, &len);
if (err)
goto out_put;
err = move_addr_to_user((struct sockaddr *)&address, len, usockaddr, usockaddr_len);
@@ -1638,15 +1629,7 @@ SYSCALL_DEFINE3(getpeername, int, fd, struct sockaddr __user *, usockaddr,
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock != NULL) {
- err = security_socket_getpeername(sock);
- if (err) {
- fput_light(sock->file, fput_needed);
- return err;
- }
-
- err =
- sock->ops->getname(sock, (struct sockaddr *)&address, &len,
- 1);
+ err = sock_getpeer(sock, (struct sockaddr *)&address, &len);
if (!err)
err = move_addr_to_user((struct sockaddr *)&address, len, usockaddr,
usockaddr_len);
--
1.6.3.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v21 072/100] c/r: introduce checkpoint/restore methods to struct proto_ops
[not found] <1272723382-19470-1-git-send-email-orenl@cs.columbia.edu>
` (2 preceding siblings ...)
2010-05-01 14:15 ` [PATCH v21 071/100] Add common socket helpers to unify the security hooks Oren Laadan
@ 2010-05-01 14:15 ` Oren Laadan
2010-05-01 14:15 ` [PATCH v21 073/100] c/r: Add AF_UNIX support (v12) Oren Laadan
` (9 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Oren Laadan @ 2010-05-01 14:15 UTC (permalink / raw)
To: Andrew Morton
Cc: containers, linux-kernel, Serge Hallyn, Matt Helsley,
Pavel Emelyanov, Oren Laadan, netdev
This adds new 'proto_ops' function for checkpointing and restoring
sockets. This allows the checkpoint/restart code to compile nicely
when, e.g., AF_UNIX sockets are selected as a module.
It also adds a function 'collecting' a socket for leak-detection
during full-container checkpoint. This is useful for those sockets
that hold references to other "collectable" objects. Two examples are
AF_UNIX buffers which reference the socket of origin, and sockets that
have file descriptors in-transit.
Cc: netdev@vger.kernel.org
Signed-off-by: Oren Laadan <orenl@cs.columbia.edu>
Acked-by: Serge E. Hallyn <serue@us.ibm.com>
Tested-by: Serge E. Hallyn <serue@us.ibm.com>
---
include/linux/net.h | 9 +++++++++
1 files changed, 9 insertions(+), 0 deletions(-)
diff --git a/include/linux/net.h b/include/linux/net.h
index 4157b5d..1f32c70 100644
--- a/include/linux/net.h
+++ b/include/linux/net.h
@@ -153,6 +153,9 @@ struct sockaddr;
struct msghdr;
struct module;
+struct ckpt_ctx;
+struct ckpt_hdr_socket;
+
struct proto_ops {
int family;
struct module *owner;
@@ -201,6 +204,12 @@ struct proto_ops {
int offset, size_t size, int flags);
ssize_t (*splice_read)(struct socket *sock, loff_t *ppos,
struct pipe_inode_info *pipe, size_t len, unsigned int flags);
+ int (*checkpoint)(struct ckpt_ctx *ctx,
+ struct socket *sock);
+ int (*collect)(struct ckpt_ctx *ctx,
+ struct socket *sock);
+ int (*restore)(struct ckpt_ctx *ctx, struct socket *sock,
+ struct ckpt_hdr_socket *h);
};
#define DECLARE_SOCKADDR(type, dst, src) \
--
1.6.3.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v21 073/100] c/r: Add AF_UNIX support (v12)
[not found] <1272723382-19470-1-git-send-email-orenl@cs.columbia.edu>
` (3 preceding siblings ...)
2010-05-01 14:15 ` [PATCH v21 072/100] c/r: introduce checkpoint/restore methods to struct proto_ops Oren Laadan
@ 2010-05-01 14:15 ` Oren Laadan
2010-05-01 14:15 ` [PATCH v21 074/100] c/r: add support for listening INET sockets (v2) Oren Laadan
` (8 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Oren Laadan @ 2010-05-01 14:15 UTC (permalink / raw)
To: Andrew Morton
Cc: containers, linux-kernel, Serge Hallyn, Matt Helsley,
Pavel Emelyanov, Dan Smith, Alexey Dobriyan, netdev, Oren Laadan
From: Dan Smith <danms@us.ibm.com>
This patch adds basic checkpoint/restart support for AF_UNIX sockets. It
has been tested with a single and multiple processes, and with data inflight
at the time of checkpoint. It supports socketpair()s, path-based, and
abstract sockets.
Changes in ckpt-v21:
- Do not include checkpoint_hdr.h explicitly
- [Dan Smith] Disable softirqs when taking the socket queue lock
Changes in ckpt-v19:
- [Serge Hallyn] skb->tail can be offset
Changes in ckpt-v19-rc3:
- Rebase to kernel 2.6.33: export and leverage sock_alloc_file()
- [Nathan Lynch] Fix net/checkpoint.c for 64-bit
Changes in ckpt-v19-rc2:
- Change select uses of ckpt_debug() to ckpt_err() in net c/r
- [Dan Smith] Unify skb read/write functions and handle fragmented buffers
- [Dan Smith] Update buffer restore code to match the new format
Changes in ckpt-v19-rc1:
- [Dan Smith] Fix compile issue with CONFIG_CHECKPOINT=n
- [Dan Smith] Remove an unnecessary check on socket restart
- [Matt Helsley] Add cpp definitions for enums
- [Dan Smith] Pass the stored sock->protocol into sock_create() on restore
Changes in v12:
- Collect sockets for leak-detection
- Adjust socket reference count during leak detection phase
Changes in v11:
- Create a struct socket for orphan socket during checkpoint
- Make sockets proper objhash objects and use checkpoint_obj() on them
- Rename headerless struct ckpt_hdr_* to struct ckpt_*
- Remove struct timeval from socket header
- Save and restore UNIX socket peer credentials
- Set socket flags on restore using sock_setsockopt() where possible
- Fail on the TIMESTAMPING_* flags for the moment (with a TODO)
- Remove other explicit flag checks that are no longer copied blindly
- Changed functions/variables names to follow existing conventions
- Use proto_ops->{checkpoint,restart} methods for af_unix
- Cleanup sock_file_restore()/sock_file_checkpoint()
- Make ckpt_hdr_socket be part of ckpt_hdr_file_socket
- Fold do_sock_file_checkpoint() into sock_file_checkpoint()
- Fold do_sock_file_restore() into sock_file_restore()
- Move sock_file_{checkpoint,restore} to net/checkpoint.c
- Properly define sock_file_{checkpoint,restore} in header file
- sock_file_restore() now calls restore_file_common()
Changes in v10:
- Moved header structure definitions back to checkpoint_hdr.h
- Moved AF_UNIX checkpoint/restart code to net/unix/checkpoint.c
- Make sock_unix_*() functions only compile if CONFIG_UNIX=y
- Add TODO for CONFIG_UNIX=m case
Changes in v9:
- Fix double-free of skb's in the list and target holding queue in the
error path of sock_copy_buffers()
- Adjust use of ckpt_read_string() to match new signature
Changes in v8:
- Fix stale dev_alloc_skb() from before the conversion to skb_clone()
- Fix a couple of broken error paths
- Fix memory leak of kvec.iov_base on successful return from sendmsg()
- Fix condition for deciding when to run sock_cptrst_verify()
- Fix buffer queue copy algorithm to hold the lock during walk(s)
- Log the errno when either getname() or getpeer() fails
- Add comments about ancillary messages in the UNIX queue
- Add TODO comments for credential restore and flags via setsockopt()
- Add TODO comment about strangely-connected dgram sockets and the use
of sendmsg(peer)
Changes in v7:
- Fix failure to free iov_base in error path of sock_read_buffer()
- Change sock_read_buffer() to use _ckpt_read_obj_type() to get the
header length and then use ckpt_kread() directly to read the payload
- Change sock_read_buffers() to sock_unix_read_buffers() and break out
some common functionality to better accommodate the subsequent INET
patch
- Generalize sock_unix_getnames() into sock_getnames() so INET can use it
- Change skb_morph() to skb_clone() which uses the more common path and
still avoids the copy
- Add check to validate the socket type before creating socket
on restore
- Comment the CAP_NET_ADMIN override in sock_read_buffer_hdr
- Strengthen the comment about priming the buffer limits
- Change the objhash functions to deny direct checkpoint of sockets and
remove the reference counting function
- Change SOCKET_BUFFERS to SOCKET_QUEUE
- Change this,peer objrefs to signed integers
- Remove names from internal socket structures
- Fix handling of sock_copy_buffers() result
- Use ckpt_fill_fname() instead of d_path() for writing CWD
- Use sock_getname() and sock_getpeer() for proper security hookage
- Return -ENOSYS for unsupported socket families in checkpoint and restart
- Use sock_setsockopt() and sock_getsockopt() where possible to save and
restore socket option values
- Check for SOCK_DESTROY flag in the global verify function because none
of our supported socket types use it
- Check for SOCK_USE_WRITE_QUEUE in AF_UNIX restore function because
that flag should not be used on such a socket
- Check socket state in UNIX restart path to validate the subset of valid
values
Changes in v6:
- Moved the socket addresses to the per-type header
- Eliminated the HASCWD flag
- Remove use of ckpt_write_err() in restart paths
- Change the order in which buffers are read so that we can set the
socket's limit equal to the size of the image's buffers (if appropriate)
and then restore the original values afterwards.
- Use the ckpt_validate_errno() helper
- Add a check to make sure that we didn't restore a (UNIX) socket with
any skb's in the send buffer
- Fix up sock_unix_join() to not leave addr uninitialized for socketpair
- Remove inclusion of checkpoint_hdr.h in the socket files
- Make sock_unix_write_cwd() use ckpt_write_string() and use the new
ckpt_read_string() for reading the cwd
- Use the restored realcred credentials in sock_unix_join()
- Fix error path of the chdir_and_bind
- Change the algorithm for reloading the socket buffers to use sendmsg()
on the socket's peer for better accounting
- For DGRAM sockets, check the backlog value against the system max
to avoid letting a restart bypass the overloaded queue length
- Use sock_bind() instead of sock->ops->bind() to gain the security hook
- Change "restart" to "restore" in some of the function names
Changes in v5:
- Change laddr and raddr buffers in socket header to be long enough
for INET6 addresses
- Place socket.c and sock.h function definitions inside #ifdef
CONFIG_CHECKPOINT
- Add explicit check in sock_unix_makeaddr() to refuse if the
checkpoint image specifies an addr length of 0
- Split sock_unix_restart() into a few pieces to facilitate:
- Changed behavior of the unix restore code so that unlinked LISTEN
sockets don't do a bind()...unlink()
- Save the base path of a bound socket's path so that we can chdir()
to the base before bind() if it is a relative path
- Call bind() for any socket that is not established but has a
non-zero-length local address
- Enforce the current sysctl limit on socket buffer size during restart
unless the user holds CAP_NET_ADMIN
- Unlink a path-based socket before calling bind()
Changes in v4:
- Changed the signdness of rcvlowat, rcvtimeo, sndtimeo, and backlog
to match their struct sock definitions. This should avoid issues
with sign extension.
- Add a sock_cptrst_verify() function to be run at restore time to
validate several of the values in the checkpoint image against
limits, flag masks, etc.
- Write an error string with ctk_write_err() in the obscure cases
- Don't write socket buffers for listen sockets
- Sanity check address lengths before we agree to allocate memory
- Check the result of inserting the peer object in the objhash on
restart
- Check return value of sock_cptrst() on restart
- Change logic in remote getname() phase of checkpoint to not fail for
closed (et al) sockets
- Eliminate the memory copy while reading socket buffers on restart
Changes in v3:
- Move sock_file_checkpoint() above sock_file_restore()
- Change __sock_file_*() functions to do_sock_file_*()
- Adjust some of the struct cr_hdr_socket alignment
- Improve the sock_copy_buffers() algorithm to avoid locking the source
queue for the entire operation
- Fix alignment in the socket header struct(s)
- Move the per-protocol structure (ckpt_hdr_socket_un) out of the
common socket header and read/write it separately
- Fix missing call to sock_cptrst() in restore path
- Break out the socket joining into another function
- Fix failure to restore the socket address thus fixing getname()
- Check the state values on restart
- Fix case of state being TCP_CLOSE, which allows dgram sockets to be
properly connected (if appropriate) to their peer and maintain the
sockaddr for getname() operation
- Fix restoring a listening socket that has been unlink()'d
- Fix checkpointing sockets with an in-flight FD-passing SKB. Fail
with EBUSY.
- Fix checkpointing listening sockets with an unaccepted connection.
Fail with EBUSY.
- Changed 'un' to 'unix' in function and structure names
Changes in v2:
- Change GFP_KERNEL to GFP_ATOMIC in sock_copy_buffers() (this seems
to be rather common in other uses of skb_copy())
- Move the ckpt_hdr_socket structure definition to linux/socket.h
- Fix whitespace issue
- Move sock_file_checkpoint() to net/socket.c for symmetry
Cc: Alexey Dobriyan <adobriyan@gmail.com>
Cc: netdev@vger.kernel.org
Acked-by: Serge Hallyn <serue@us.ibm.com>
Acked-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Dan Smith <danms@us.ibm.com>
Signed-off-by: Oren Laadan <orenl@cs.columbia.edu>
---
fs/checkpoint.c | 7 +
include/linux/checkpoint.h | 8 +
include/linux/checkpoint_hdr.h | 119 +++++-
include/linux/net.h | 2 +
include/net/af_unix.h | 15 +
include/net/sock.h | 10 +
kernel/checkpoint/objhash.c | 26 +-
net/Makefile | 2 +
net/checkpoint.c | 1028 ++++++++++++++++++++++++++++++++++++++++
net/socket.c | 6 +-
net/unix/Makefile | 1 +
net/unix/af_unix.c | 9 +
net/unix/checkpoint.c | 646 +++++++++++++++++++++++++
13 files changed, 1872 insertions(+), 7 deletions(-)
create mode 100644 net/checkpoint.c
create mode 100644 net/unix/checkpoint.c
diff --git a/fs/checkpoint.c b/fs/checkpoint.c
index 783c920..23ec4de 100644
--- a/fs/checkpoint.c
+++ b/fs/checkpoint.c
@@ -21,6 +21,7 @@
#include <linux/syscalls.h>
#include <linux/deferqueue.h>
#include <linux/checkpoint.h>
+#include <net/sock.h>
/**************************************************************************
* Checkpoint
@@ -619,6 +620,12 @@ static struct restore_file_ops restore_file_ops[] = {
.file_type = CKPT_FILE_FIFO,
.restore = fifo_file_restore,
},
+ /* socket */
+ {
+ .file_name = "SOCKET",
+ .file_type = CKPT_FILE_SOCKET,
+ .restore = sock_file_restore,
+ },
};
static void *restore_file(struct ckpt_ctx *ctx)
diff --git a/include/linux/checkpoint.h b/include/linux/checkpoint.h
index 549f133..25275af 100644
--- a/include/linux/checkpoint.h
+++ b/include/linux/checkpoint.h
@@ -33,6 +33,7 @@
#include <linux/checkpoint_types.h>
#include <linux/checkpoint_hdr.h>
#include <linux/err.h>
+#include <net/sock.h>
/* sycall helpers */
extern long do_sys_checkpoint(pid_t pid, int fd,
@@ -97,6 +98,13 @@ extern int restore_read_page(struct ckpt_ctx *ctx, struct page *page);
/* pids */
extern pid_t ckpt_pid_nr(struct ckpt_ctx *ctx, struct pid *pid);
+/* socket functions */
+extern int ckpt_sock_getnames(struct ckpt_ctx *ctx,
+ struct socket *socket,
+ struct sockaddr *loc, unsigned *loc_len,
+ struct sockaddr *rem, unsigned *rem_len);
+extern struct sk_buff *sock_restore_skb(struct ckpt_ctx *ctx);
+
/* ckpt kflags */
#define ckpt_set_ctx_kflag(__ctx, __kflag) \
set_bit(__kflag##_BIT, &(__ctx)->kflags)
diff --git a/include/linux/checkpoint_hdr.h b/include/linux/checkpoint_hdr.h
index e706636..2be2d2c 100644
--- a/include/linux/checkpoint_hdr.h
+++ b/include/linux/checkpoint_hdr.h
@@ -10,18 +10,22 @@
* distribution for more details.
*/
-#ifndef __KERNEL__
-#include <sys/types.h>
-#include <linux/types.h>
-#endif
-
#ifdef __KERNEL__
#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/un.h>
#ifndef CONFIG_CHECKPOINT
#error linux/checkpoint_hdr.h included directly (without CONFIG_CHECKPOINT)
#endif
+#else /* __KERNEL__ */
+
+#include <sys/types.h>
+#include <linux/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
#endif
/*
@@ -145,6 +149,17 @@ enum {
CKPT_HDR_SIGPENDING,
#define CKPT_HDR_SIGPENDING CKPT_HDR_SIGPENDING
+ CKPT_HDR_SOCKET = 701,
+#define CKPT_HDR_SOCKET CKPT_HDR_SOCKET
+ CKPT_HDR_SOCKET_QUEUE,
+#define CKPT_HDR_SOCKET_QUEUE CKPT_HDR_SOCKET_QUEUE
+ CKPT_HDR_SOCKET_BUFFER,
+#define CKPT_HDR_SOCKET_BUFFER CKPT_HDR_SOCKET_BUFFER
+ CKPT_HDR_SOCKET_FRAG,
+#define CKPT_HDR_SOCKET_FRAG CKPT_HDR_SOCKET_FRAG
+ CKPT_HDR_SOCKET_UNIX,
+#define CKPT_HDR_SOCKET_UNIX CKPT_HDR_SOCKET_UNIX
+
CKPT_HDR_TAIL = 9001,
#define CKPT_HDR_TAIL CKPT_HDR_TAIL
@@ -200,6 +215,8 @@ enum obj_type {
#define CKPT_OBJ_USER CKPT_OBJ_USER
CKPT_OBJ_GROUPINFO,
#define CKPT_OBJ_GROUPINFO CKPT_OBJ_GROUPINFO
+ CKPT_OBJ_SOCK,
+#define CKPT_OBJ_SOCK CKPT_OBJ_SOCK
CKPT_OBJ_MAX
#define CKPT_OBJ_MAX CKPT_OBJ_MAX
};
@@ -449,6 +466,8 @@ enum file_type {
#define CKPT_FILE_PIPE CKPT_FILE_PIPE
CKPT_FILE_FIFO,
#define CKPT_FILE_FIFO CKPT_FILE_FIFO
+ CKPT_FILE_SOCKET,
+#define CKPT_FILE_SOCKET CKPT_FILE_SOCKET
CKPT_FILE_MAX
#define CKPT_FILE_MAX CKPT_FILE_MAX
};
@@ -473,6 +492,96 @@ struct ckpt_hdr_file_pipe {
__s32 pipe_objref;
} __attribute__((aligned(8)));
+/* socket */
+struct ckpt_hdr_socket {
+ struct ckpt_hdr h;
+
+ struct { /* struct socket */
+ __u64 flags;
+ __u8 state;
+ } socket __attribute__ ((aligned(8)));
+
+ struct { /* struct sock_common */
+ __u32 bound_dev_if;
+ __u32 reuse;
+ __u16 family;
+ __u8 state;
+ } sock_common __attribute__ ((aligned(8)));
+
+ struct { /* struct sock */
+ __s64 rcvlowat;
+ __u64 flags;
+
+ __s64 rcvtimeo;
+ __s64 sndtimeo;
+
+ __u32 err;
+ __u32 err_soft;
+ __u32 priority;
+ __s32 rcvbuf;
+ __s32 sndbuf;
+ __u16 type;
+ __s16 backlog;
+
+ __u8 protocol;
+ __u8 state;
+ __u8 shutdown;
+ __u8 userlocks;
+ __u8 no_check;
+
+ struct linger linger;
+ } sock __attribute__ ((aligned(8)));
+} __attribute__ ((aligned(8)));
+
+struct ckpt_hdr_socket_queue {
+ struct ckpt_hdr h;
+ __u32 skb_count;
+ __u32 total_bytes;
+} __attribute__ ((aligned(8)));
+
+struct ckpt_hdr_socket_buffer {
+ struct ckpt_hdr h;
+ __u32 transport_header;
+ __u32 network_header;
+ __u32 mac_header;
+ __u32 lin_len; /* Length of linear data */
+ __u32 frg_len; /* Length of fragment data */
+ __u32 skb_len; /* Length of skb (adjusted) */
+ __u32 hdr_len; /* Length of skipped header */
+ __u32 mac_len;
+ __u32 data_offset; /* Offset of data pointer from head */
+ __s32 sk_objref;
+ __s32 pr_objref;
+ __u16 protocol;
+ __u16 nr_frags;
+ __u8 cb[48];
+};
+
+struct ckpt_hdr_socket_buffer_frag {
+ struct ckpt_hdr h;
+ __u32 size;
+ __u32 offset;
+};
+
+#define CKPT_UNIX_LINKED 1
+struct ckpt_hdr_socket_unix {
+ struct ckpt_hdr h;
+ __s32 this;
+ __s32 peer;
+ __u32 peercred_uid;
+ __u32 peercred_gid;
+ __u32 flags;
+ __u32 laddr_len;
+ __u32 raddr_len;
+ struct sockaddr_un laddr;
+ struct sockaddr_un raddr;
+} __attribute__ ((aligned(8)));
+
+struct ckpt_hdr_file_socket {
+ struct ckpt_hdr_file common;
+ __s32 sock_objref;
+} __attribute__((aligned(8)));
+
/* memory layout */
struct ckpt_hdr_mm {
struct ckpt_hdr h;
diff --git a/include/linux/net.h b/include/linux/net.h
index 1f32c70..6ffe827 100644
--- a/include/linux/net.h
+++ b/include/linux/net.h
@@ -246,6 +246,8 @@ extern int sock_sendmsg(struct socket *sock, struct msghdr *msg,
size_t len);
extern int sock_recvmsg(struct socket *sock, struct msghdr *msg,
size_t size, int flags);
+extern int sock_alloc_file(struct socket *sock, struct file **f,
+ int flags);
extern int sock_map_fd(struct socket *sock, int flags);
extern struct socket *sockfd_lookup(int fd, int *err);
#define sockfd_put(sock) fput(sock->file)
diff --git a/include/net/af_unix.h b/include/net/af_unix.h
index 1614d78..ee423d1 100644
--- a/include/net/af_unix.h
+++ b/include/net/af_unix.h
@@ -68,4 +68,19 @@ static inline int unix_sysctl_register(struct net *net) { return 0; }
static inline void unix_sysctl_unregister(struct net *net) {}
#endif
#endif
+
+#ifdef CONFIG_CHECKPOINT
+struct ckpt_ctx;
+struct ckpt_hdr_socket;
+extern int unix_checkpoint(struct ckpt_ctx *ctx, struct socket *sock);
+extern int unix_restore(struct ckpt_ctx *ctx, struct socket *sock,
+ struct ckpt_hdr_socket *h);
+extern int unix_collect(struct ckpt_ctx *ctx, struct socket *sock);
+
+#else
+#define unix_checkpoint NULL
+#define unix_restore NULL
+#define unix_collect NULL
+#endif /* CONFIG_CHECKPOINT */
+
#endif
diff --git a/include/net/sock.h b/include/net/sock.h
index 3cf7de4..1c7665a 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h
@@ -1713,4 +1713,14 @@ extern int sysctl_optmem_max;
extern __u32 sysctl_wmem_default;
extern __u32 sysctl_rmem_default;
+#ifdef CONFIG_CHECKPOINT
+/* Checkpoint/Restart Functions */
+struct ckpt_ctx;
+struct ckpt_hdr_file;
+extern int sock_file_checkpoint(struct ckpt_ctx *ctx, struct file *file);
+extern struct file *sock_file_restore(struct ckpt_ctx *ctx,
+ struct ckpt_hdr_file *h);
+extern int sock_file_collect(struct ckpt_ctx *ctx, struct file *file);
+#endif
+
#endif /* _SOCK_H */
diff --git a/kernel/checkpoint/objhash.c b/kernel/checkpoint/objhash.c
index 0fe741b..4960c25 100644
--- a/kernel/checkpoint/objhash.c
+++ b/kernel/checkpoint/objhash.c
@@ -29,7 +29,7 @@ struct ckpt_obj {
struct hlist_node next;
};
-/* object internal flags */
+/*` object internal flags */
#define CKPT_OBJ_CHECKPOINTED 0x1 /* object already checkpointed */
#define CKPT_OBJ_VISITED 0x2 /* object already visited */
@@ -481,6 +481,26 @@ static void ckpt_obj_users_inc(struct ckpt_ctx *ctx, void *ptr, int increment)
*/
/**
+ * obj_sock_adjust_users - remove implicit reference on DEAD sockets
+ * @obj: CKPT_OBJ_SOCK object to adjust
+ *
+ * Sockets that have been disconnected from their struct file have
+ * a reference count one less than normal sockets. The objhash's
+ * assumption of such a reference is therefore incorrect, so we correct
+ * it here.
+ */
+static inline void obj_sock_adjust_users(struct ckpt_obj *obj)
+{
+ struct sock *sk = (struct sock *)obj->ptr;
+
+ if (sock_flag(sk, SOCK_DEAD)) {
+ obj->users--;
+ ckpt_debug("Adjusting SOCK %i count to %i\n",
+ obj->objref, obj->users);
+ }
+}
+
+/**
* ckpt_obj_contained - test if shared objects are contained in checkpoint
* @ctx: checkpoint context
*
@@ -505,6 +525,10 @@ int ckpt_obj_contained(struct ckpt_ctx *ctx)
hlist_for_each_entry(obj, node, &ctx->obj_hash->list, next) {
if (!obj->ops->ref_users)
continue;
+
+ if (obj->ops->obj_type == CKPT_OBJ_SOCK)
+ obj_sock_adjust_users(obj);
+
if (obj->ops->ref_users(obj->ptr) != obj->users) {
ckpt_err(ctx, -EBUSY,
"%(O)%(P)%(S)Usage leak (%d != %d)\n",
diff --git a/net/Makefile b/net/Makefile
index 1542e72..74b038f 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -65,3 +65,5 @@ ifeq ($(CONFIG_NET),y)
obj-$(CONFIG_SYSCTL) += sysctl_net.o
endif
obj-$(CONFIG_WIMAX) += wimax/
+
+obj-$(CONFIG_CHECKPOINT) += checkpoint.o
diff --git a/net/checkpoint.c b/net/checkpoint.c
new file mode 100644
index 0000000..9116d7a
--- /dev/null
+++ b/net/checkpoint.c
@@ -0,0 +1,1028 @@
+/*
+ * Copyright 2009 IBM Corporation
+ *
+ * Author(s): Dan Smith <danms@us.ibm.com>
+ * Oren Laadan <orenl@cs.columbia.edu>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#include <linux/socket.h>
+#include <linux/mount.h>
+#include <linux/file.h>
+#include <linux/namei.h>
+#include <linux/syscalls.h>
+#include <linux/sched.h>
+#include <linux/fs_struct.h>
+#include <linux/highmem.h>
+
+#include <net/af_unix.h>
+#include <net/tcp_states.h>
+#include <net/tcp.h>
+
+#include <linux/deferqueue.h>
+#include <linux/checkpoint.h>
+
+struct dq_buffers {
+ struct ckpt_ctx *ctx;
+ struct sock *sk;
+};
+
+static int sock_copy_buffers(struct sk_buff_head *from,
+ struct sk_buff_head *to,
+ uint32_t *total_bytes)
+{
+ int count1 = 0;
+ int count2 = 0;
+ int i;
+ struct sk_buff *skb;
+ struct sk_buff **skbs;
+
+ *total_bytes = 0;
+
+ spin_lock_bh(&from->lock);
+ skb_queue_walk(from, skb)
+ count1++;
+ spin_unlock_bh(&from->lock);
+
+ skbs = kzalloc(sizeof(*skbs) * count1, GFP_KERNEL);
+ if (!skbs)
+ return -ENOMEM;
+
+ for (i = 0; i < count1; i++) {
+ skbs[i] = dev_alloc_skb(0);
+ if (!skbs[i])
+ goto err;
+ }
+
+ i = 0;
+ spin_lock_bh(&from->lock);
+ skb_queue_walk(from, skb) {
+ if (++count2 > count1)
+ break; /* The queue changed as we read it */
+
+ skb_morph(skbs[i], skb);
+ skbs[i]->sk = skb->sk;
+ skb_queue_tail(to, skbs[i]);
+
+ *total_bytes += skb->len;
+ i++;
+ }
+ spin_unlock_bh(&from->lock);
+
+ if (count1 != count2)
+ goto err;
+
+ kfree(skbs);
+
+ return count1;
+ err:
+ while (skb_dequeue(to))
+ ; /* Pull all the buffers out of the queue */
+ for (i = 0; i < count1; i++)
+ kfree_skb(skbs[i]);
+ kfree(skbs);
+
+ return -EAGAIN;
+}
+
+static void sock_record_header_info(struct sk_buff *skb,
+ struct ckpt_hdr_socket_buffer *h)
+{
+
+ h->mac_len = skb->mac_len;
+ h->skb_len = skb->len;
+ h->hdr_len = skb->data - skb->head;
+ h->frg_len = skb->data_len;
+ h->data_offset = (skb->data - skb->head);
+
+#ifdef NET_SKBUFF_DATA_USES_OFFSET
+ h->transport_header = skb->transport_header;
+ h->network_header = skb->network_header;
+ h->mac_header = skb->mac_header;
+ h->lin_len = (unsigned long) skb->tail;
+#else
+ h->transport_header = skb->transport_header - skb->head;
+ h->network_header = skb->network_header - skb->head;
+ h->mac_header = skb->mac_header - skb->head;
+ h->lin_len = ((unsigned long) skb->tail - (unsigned long) skb->head);
+#endif
+
+ memcpy(h->cb, skb->cb, sizeof(skb->cb));
+ h->nr_frags = skb_shinfo(skb)->nr_frags;
+}
+
+int sock_restore_header_info(struct ckpt_ctx *ctx,
+ struct sk_buff *skb,
+ struct ckpt_hdr_socket_buffer *h)
+{
+ if (h->mac_header + h->mac_len != h->network_header) {
+ ckpt_err(ctx, -EINVAL,
+ "skb mac_header %u+%u != network header %u\n",
+ h->mac_header, h->mac_len, h->network_header);
+ return -EINVAL;
+ }
+
+ if (h->network_header > h->lin_len) {
+ ckpt_err(ctx, -EINVAL,
+ "skb network header %u > linear length %u\n",
+ h->network_header, h->lin_len);
+ return -EINVAL;
+ }
+
+ if (h->transport_header > h->lin_len) {
+ ckpt_err(ctx, -EINVAL,
+ "skb transport header %u > linear length %u\n",
+ h->transport_header, h->lin_len);
+ return -EINVAL;
+ }
+
+ if (h->data_offset > h->lin_len) {
+ ckpt_err(ctx, -EINVAL,
+ "skb data offset %u > linear length %u\n",
+ h->data_offset, h->lin_len);
+ return -EINVAL;
+ }
+
+ if (h->skb_len > SKB_MAX_ALLOC) {
+ ckpt_err(ctx, -EINVAL,
+ "skb total length %u larger than max of %lu\n",
+ h->skb_len, SKB_MAX_ALLOC);
+ return -EINVAL;
+ }
+
+ skb_set_transport_header(skb, h->transport_header);
+ skb_set_network_header(skb, h->network_header);
+ skb_set_mac_header(skb, h->mac_header);
+ skb->mac_len = h->mac_len;
+
+ /* FIXME: This should probably be sanitized per-protocol to
+ * make sure nothing bad happens if it is hijacked. For the
+ * current set of protocols that we restore this way, the data
+ * contained within is not very risky (flags and sequence
+ * numbers) but could still be evalutated from a
+ * could-the-user- have-set-these-flags point of view.
+ */
+ memcpy(skb->cb, h->cb, sizeof(skb->cb));
+
+ skb->data = skb->head + h->data_offset;
+ skb->len = h->skb_len;
+
+ return 0;
+}
+
+static int sock_restore_skb_frag(struct ckpt_ctx *ctx,
+ struct sk_buff *skb,
+ int frag_idx)
+{
+ struct ckpt_hdr_socket_buffer_frag *h;
+ struct page *page;
+ int ret = 0;
+
+ h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_SOCKET_FRAG);
+ if (IS_ERR(h)) {
+ ckpt_err(ctx, PTR_ERR(h), "failed to read buffer object\n");
+ return PTR_ERR(h);
+ }
+
+ if ((h->size > PAGE_SIZE) || (h->offset >= PAGE_SIZE)) {
+ ret = -EINVAL;
+ ckpt_err(ctx, ret, "skb frag size=%i,offset=%i > PAGE_SIZE\n",
+ h->size, h->offset);
+ goto out;
+ }
+
+ page = alloc_page(GFP_KERNEL);
+ if (!page) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = restore_read_page(ctx, page);
+ if (ret) {
+ ckpt_err(ctx, ret, "failed to read fragment: %i\n", ret);
+ __free_page(page);
+ } else {
+ ckpt_debug("read %i+%i for fragment %i\n",
+ h->offset, h->size, frag_idx);
+ skb_add_rx_frag(skb, frag_idx, page, h->offset, h->size);
+ ret = h->size;
+ }
+ out:
+ ckpt_hdr_put(ctx, h);
+ return ret;
+}
+
+struct sk_buff *sock_restore_skb(struct ckpt_ctx *ctx)
+{
+ struct ckpt_hdr_socket_buffer *h;
+ struct sk_buff *skb = NULL;
+ int i, ret;
+
+ h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_SOCKET_BUFFER);
+ if (IS_ERR(h))
+ return (struct sk_buff *)h;
+
+ ret = -ENOSPC;
+ if (h->lin_len > SKB_MAX_ALLOC) {
+ ckpt_err(ctx, ret, "socket linear buffer too big (%u > %lu)\n",
+ h->lin_len, SKB_MAX_ALLOC);
+ goto out;
+ } else if (h->frg_len > SKB_MAX_ALLOC) {
+ ckpt_err(ctx, ret, "socket frag size too big (%u > %lu\n",
+ h->frg_len, SKB_MAX_ALLOC);
+ goto out;
+ } else if (h->nr_frags >= MAX_SKB_FRAGS) {
+ ckpt_err(ctx, ret, "socket frag count too big (%u > %lu\n",
+ h->nr_frags, MAX_SKB_FRAGS);
+ goto out;
+ }
+
+ skb = alloc_skb(h->lin_len, GFP_KERNEL);
+ if (!skb) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = _ckpt_read_obj_type(ctx, skb_put(skb, h->lin_len),
+ h->lin_len, CKPT_HDR_BUFFER);
+ ckpt_debug("read linear skb length %u: %i\n", h->lin_len, ret);
+ if (ret < 0)
+ goto out;
+
+ for (i = 0; i < h->nr_frags; i++) {
+ ret = sock_restore_skb_frag(ctx, skb, i);
+ ckpt_debug("read skb frag %i/%i: %i\n",
+ i + 1, h->nr_frags, ret);
+ if (ret < 0)
+ goto out;
+ h->frg_len -= ret;
+ }
+
+ if (h->frg_len != 0) {
+ ret = -EINVAL;
+ ckpt_err(ctx, ret, "length %u remaining after reading frags\n",
+ h->frg_len);
+ goto out;
+ }
+
+ sock_restore_header_info(ctx, skb, h);
+ out:
+ ckpt_hdr_put(ctx, h);
+ if (ret < 0) {
+ kfree_skb(skb);
+ skb = ERR_PTR(ret);
+ }
+
+ return skb;
+}
+
+static int __sock_write_skb_frag(struct ckpt_ctx *ctx, skb_frag_t *frag)
+{
+ struct ckpt_hdr_socket_buffer_frag *h;
+ int ret;
+
+ h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_SOCKET_FRAG);
+ if (!h)
+ return -ENOMEM;
+
+ h->size = frag->size;
+ h->offset = frag->page_offset;
+
+ ret = ckpt_write_obj(ctx, (struct ckpt_hdr *)h);
+ ckpt_hdr_put(ctx, h);
+ if (ret < 0)
+ return ret;
+
+ ret = checkpoint_dump_page(ctx, frag->page);
+ ckpt_debug("writing frag page: %i\n", ret);
+ return ret;
+}
+
+static int __sock_write_skb(struct ckpt_ctx *ctx,
+ struct sk_buff *skb,
+ int dst_objref)
+{
+ struct ckpt_hdr_socket_buffer *h;
+ int ret = 0;
+ int i;
+
+ h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_SOCKET_BUFFER);
+ if (!h)
+ return -ENOMEM;
+
+ if (dst_objref > 0) {
+ BUG_ON(!skb->sk);
+ ret = checkpoint_obj(ctx, skb->sk, CKPT_OBJ_SOCK);
+ if (ret < 0)
+ goto out;
+ h->sk_objref = ret;
+ h->pr_objref = dst_objref;
+ }
+
+ sock_record_header_info(skb, h);
+
+ ret = ckpt_write_obj(ctx, (struct ckpt_hdr *) h);
+ if (ret < 0)
+ goto out;
+
+ ret = ckpt_write_obj_type(ctx, skb->head, h->lin_len, CKPT_HDR_BUFFER);
+ ckpt_debug("writing skb linear region %u: %i\n", h->lin_len, ret);
+ if (ret < 0)
+ goto out;
+
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+
+ ret = __sock_write_skb_frag(ctx, frag);
+ ckpt_debug("writing buffer fragment %i/%i (%i)\n",
+ i + 1, h->nr_frags, ret);
+ if (ret < 0)
+ goto out;
+ h->frg_len -= frag->size;
+ }
+
+ WARN_ON(h->frg_len != 0);
+ out:
+ ckpt_hdr_put(ctx, h);
+ return ret;
+}
+
+static int __sock_write_buffers(struct ckpt_ctx *ctx,
+ struct sk_buff_head *queue,
+ uint16_t family,
+ int dst_objref)
+{
+ struct sk_buff *skb;
+
+ skb_queue_walk(queue, skb) {
+ int ret = 0;
+
+ if (UNIXCB(skb).fp) {
+ ckpt_err(ctx, -EBUSY, "%(T)af_unix: pass fd\n");
+ return -EBUSY;
+ }
+
+ /* The other ancillary messages UNIX are always
+ * present unlike descriptors. Even though we can't
+ * detect them and fail the checkpoint, we're not at
+ * risk because we don't restore the control
+ * information in the UNIX code.
+ */
+
+ ret = __sock_write_skb(ctx, skb, dst_objref);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sock_write_buffers(struct ckpt_ctx *ctx,
+ struct sk_buff_head *queue,
+ uint16_t family,
+ int dst_objref)
+{
+ struct ckpt_hdr_socket_queue *h;
+ struct sk_buff_head tmpq;
+ int ret = -ENOMEM;
+
+ h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_SOCKET_QUEUE);
+ if (!h)
+ return -ENOMEM;
+
+ skb_queue_head_init(&tmpq);
+
+ ret = sock_copy_buffers(queue, &tmpq, &h->total_bytes);
+ if (ret < 0)
+ goto out;
+
+ h->skb_count = ret;
+ ret = ckpt_write_obj(ctx, (struct ckpt_hdr *) h);
+ if (!ret)
+ ret = __sock_write_buffers(ctx, &tmpq, family, dst_objref);
+
+ out:
+ ckpt_hdr_put(ctx, h);
+ __skb_queue_purge(&tmpq);
+
+ return ret;
+}
+
+int sock_deferred_write_buffers(void *data)
+{
+ struct dq_buffers *dq = (struct dq_buffers *)data;
+ struct ckpt_ctx *ctx = dq->ctx;
+ int ret;
+ int dst_objref;
+
+ dst_objref = ckpt_obj_lookup(ctx, dq->sk, CKPT_OBJ_SOCK);
+ if (dst_objref < 0) {
+ ckpt_err(ctx, dst_objref, "%(T)socket: owner gone?\n");
+ return dst_objref;
+ }
+
+ ret = sock_write_buffers(ctx, &dq->sk->sk_receive_queue,
+ dq->sk->sk_family, dst_objref);
+ ckpt_debug("write recv buffers: %i\n", ret);
+ if (ret < 0)
+ return ret;
+
+ ret = sock_write_buffers(ctx, &dq->sk->sk_write_queue,
+ dq->sk->sk_family, dst_objref);
+ ckpt_debug("write send buffers: %i\n", ret);
+
+ return ret;
+}
+
+int sock_defer_write_buffers(struct ckpt_ctx *ctx, struct sock *sk)
+{
+ struct dq_buffers dq;
+
+ dq.ctx = ctx;
+ dq.sk = sk;
+
+ /* NB: This is safe to do inside deferqueue_run() since it uses
+ * list_for_each_safe()
+ */
+ return deferqueue_add(ctx->files_deferq, &dq, sizeof(dq),
+ sock_deferred_write_buffers, NULL);
+}
+
+int ckpt_sock_getnames(struct ckpt_ctx *ctx, struct socket *sock,
+ struct sockaddr *loc, unsigned *loc_len,
+ struct sockaddr *rem, unsigned *rem_len)
+{
+ int ret;
+
+ ret = sock_getname(sock, loc, loc_len);
+ if (ret) {
+ ckpt_err(ctx, ret, "%(T)%(P)socket: getname local\n", sock);
+ return -EINVAL;
+ }
+
+ ret = sock_getpeer(sock, rem, rem_len);
+ if (ret) {
+ if ((sock->sk->sk_type != SOCK_DGRAM) &&
+ (sock->sk->sk_state == TCP_ESTABLISHED)) {
+ ckpt_err(ctx, ret, "%(T)%(P)socket: getname peer\n",
+ sock);
+ return -EINVAL;
+ }
+ *rem_len = 0;
+ }
+
+ return 0;
+}
+
+static int sock_cptrst_verify(struct ckpt_hdr_socket *h)
+{
+ uint8_t userlocks_mask =
+ SOCK_SNDBUF_LOCK | SOCK_RCVBUF_LOCK |
+ SOCK_BINDADDR_LOCK | SOCK_BINDPORT_LOCK;
+
+ if (h->sock.shutdown & ~SHUTDOWN_MASK)
+ return -EINVAL;
+ if (h->sock.userlocks & ~userlocks_mask)
+ return -EINVAL;
+ if (!ckpt_validate_errno(h->sock.err))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int sock_cptrst_opt(int op, struct socket *sock,
+ int optname, char *opt, int len)
+{
+ mm_segment_t fs;
+ int ret;
+
+ fs = get_fs();
+ set_fs(KERNEL_DS);
+
+ if (op == CKPT_CPT)
+ ret = sock_getsockopt(sock, SOL_SOCKET, optname, opt, &len);
+ else
+ ret = sock_setsockopt(sock, SOL_SOCKET, optname, opt, len);
+
+ set_fs(fs);
+
+ return ret;
+}
+
+#define CKPT_COPY_SOPT(op, sk, name, opt) \
+ sock_cptrst_opt(op, sk->sk_socket, name, (char *)opt, sizeof(*opt))
+
+static int sock_cptrst_bufopts(int op, struct sock *sk,
+ struct ckpt_hdr_socket *h)
+{
+ if (CKPT_COPY_SOPT(op, sk, SO_RCVBUF, &h->sock.rcvbuf))
+ if ((op == CKPT_RST) &&
+ CKPT_COPY_SOPT(op, sk, SO_RCVBUFFORCE, &h->sock.rcvbuf)) {
+ ckpt_debug("Failed to set SO_RCVBUF");
+ return -EINVAL;
+ }
+
+ if (CKPT_COPY_SOPT(op, sk, SO_SNDBUF, &h->sock.sndbuf))
+ if ((op == CKPT_RST) &&
+ CKPT_COPY_SOPT(op, sk, SO_SNDBUFFORCE, &h->sock.sndbuf)) {
+ ckpt_debug("Failed to set SO_SNDBUF");
+ return -EINVAL;
+ }
+
+ /* It's silly that we have to fight ourselves here, but
+ * sock_setsockopt() doubles the initial value, so divide here
+ * to store the user's value and avoid doubling on restart
+ */
+ if ((op == CKPT_CPT) && (h->sock.rcvbuf != SOCK_MIN_RCVBUF))
+ h->sock.rcvbuf >>= 1;
+
+ if ((op == CKPT_CPT) && (h->sock.sndbuf != SOCK_MIN_SNDBUF))
+ h->sock.sndbuf >>= 1;
+
+ return 0;
+}
+
+struct sock_flag_mapping {
+ int opt;
+ int flag;
+};
+
+struct sock_flag_mapping sk_flag_map[] = {
+ {SO_OOBINLINE, SOCK_URGINLINE},
+ {SO_KEEPALIVE, SOCK_KEEPOPEN},
+ {SO_BROADCAST, SOCK_BROADCAST},
+ {SO_TIMESTAMP, SOCK_RCVTSTAMP},
+ {SO_TIMESTAMPNS, SOCK_RCVTSTAMPNS},
+ {SO_DEBUG, SOCK_DBG},
+ {SO_DONTROUTE, SOCK_LOCALROUTE},
+};
+
+struct sock_flag_mapping sock_flag_map[] = {
+ {SO_PASSCRED, SOCK_PASSCRED},
+};
+
+static int sock_restore_flag(struct socket *sock,
+ unsigned long *flags,
+ int flag,
+ int option)
+{
+ int v = 1;
+ int ret = 0;
+
+ if (test_and_clear_bit(flag, flags))
+ ret = sock_setsockopt(sock, SOL_SOCKET, option,
+ (char *)&v, sizeof(v));
+
+ return ret;
+}
+
+
+static int sock_restore_flags(struct socket *sock, struct ckpt_hdr_socket *h)
+{
+ unsigned long sk_flags = h->sock.flags;
+ unsigned long sock_flags = h->socket.flags;
+ int ret;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(sk_flag_map); i++) {
+ int opt = sk_flag_map[i].opt;
+ int flag = sk_flag_map[i].flag;
+ ret = sock_restore_flag(sock, &sk_flags, flag, opt);
+ if (ret) {
+ ckpt_debug("Failed to set skopt %i: %i\n", opt, ret);
+ return ret;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sock_flag_map); i++) {
+ int opt = sock_flag_map[i].opt;
+ int flag = sock_flag_map[i].flag;
+ ret = sock_restore_flag(sock, &sock_flags, flag, opt);
+ if (ret) {
+ ckpt_debug("Failed to set sockopt %i: %i\n", opt, ret);
+ return ret;
+ }
+ }
+
+ /* TODO: Handle SOCK_TIMESTAMPING_* flags */
+ if (test_bit(SOCK_TIMESTAMPING_TX_HARDWARE, &sk_flags) ||
+ test_bit(SOCK_TIMESTAMPING_TX_SOFTWARE, &sk_flags) ||
+ test_bit(SOCK_TIMESTAMPING_RX_HARDWARE, &sk_flags) ||
+ test_bit(SOCK_TIMESTAMPING_RX_SOFTWARE, &sk_flags) ||
+ test_bit(SOCK_TIMESTAMPING_SOFTWARE, &sk_flags) ||
+ test_bit(SOCK_TIMESTAMPING_RAW_HARDWARE, &sk_flags) ||
+ test_bit(SOCK_TIMESTAMPING_SYS_HARDWARE, &sk_flags)) {
+ ckpt_debug("SOF_TIMESTAMPING_* flags are not supported\n");
+ return -ENOSYS;
+ }
+
+ if (test_and_clear_bit(SOCK_DEAD, &sk_flags))
+ sock_set_flag(sock->sk, SOCK_DEAD);
+
+
+ /* Anything that is still set in the flags that isn't part of
+ * our protocol's default set, indicates an error
+ */
+ if (sk_flags & ~sock->sk->sk_flags) {
+ ckpt_debug("Unhandled sock flags: %lx\n", sk_flags);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sock_copy_timeval(int op, struct sock *sk,
+ int sockopt, __s64 *saved)
+{
+ struct timeval tv;
+
+ if (op == CKPT_CPT) {
+ if (CKPT_COPY_SOPT(op, sk, sockopt, &tv))
+ return -EINVAL;
+ *saved = timeval_to_ns(&tv);
+ } else {
+ tv = ns_to_timeval(*saved);
+ if (CKPT_COPY_SOPT(op, sk, sockopt, &tv))
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sock_cptrst(struct ckpt_ctx *ctx, struct sock *sk,
+ struct ckpt_hdr_socket *h, int op)
+{
+ if (sk->sk_socket)
+ CKPT_COPY(op, h->socket.state, sk->sk_socket->state);
+
+ CKPT_COPY(op, h->sock_common.bound_dev_if, sk->sk_bound_dev_if);
+ CKPT_COPY(op, h->sock_common.family, sk->sk_family);
+
+ CKPT_COPY(op, h->sock.shutdown, sk->sk_shutdown);
+ CKPT_COPY(op, h->sock.userlocks, sk->sk_userlocks);
+ CKPT_COPY(op, h->sock.no_check, sk->sk_no_check);
+ CKPT_COPY(op, h->sock.protocol, sk->sk_protocol);
+ CKPT_COPY(op, h->sock.err, sk->sk_err);
+ CKPT_COPY(op, h->sock.err_soft, sk->sk_err_soft);
+ CKPT_COPY(op, h->sock.type, sk->sk_type);
+ CKPT_COPY(op, h->sock.state, sk->sk_state);
+ CKPT_COPY(op, h->sock.backlog, sk->sk_max_ack_backlog);
+
+ if (sock_cptrst_bufopts(op, sk, h))
+ return -EINVAL;
+
+ if (CKPT_COPY_SOPT(op, sk, SO_REUSEADDR, &h->sock_common.reuse)) {
+ ckpt_err(ctx, -EINVAL, "Failed to set SO_REUSEADDR");
+
+ return -EINVAL;
+ }
+
+ if (CKPT_COPY_SOPT(op, sk, SO_PRIORITY, &h->sock.priority)) {
+ ckpt_err(ctx, -EINVAL, "Failed to set SO_PRIORITY");
+ return -EINVAL;
+ }
+
+ if (CKPT_COPY_SOPT(op, sk, SO_RCVLOWAT, &h->sock.rcvlowat)) {
+ ckpt_err(ctx, -EINVAL, "Failed to set SO_RCVLOWAT");
+ return -EINVAL;
+ }
+
+ if (CKPT_COPY_SOPT(op, sk, SO_LINGER, &h->sock.linger)) {
+ ckpt_err(ctx, -EINVAL, "Failed to set SO_LINGER");
+ return -EINVAL;
+ }
+
+ if (sock_copy_timeval(op, sk, SO_SNDTIMEO, &h->sock.sndtimeo)) {
+ ckpt_err(ctx, -EINVAL, "Failed to set SO_SNDTIMEO");
+ return -EINVAL;
+ }
+
+ if (sock_copy_timeval(op, sk, SO_RCVTIMEO, &h->sock.rcvtimeo)) {
+ ckpt_err(ctx, -EINVAL, "Failed to set SO_RCVTIMEO");
+ return -EINVAL;
+ }
+
+ if (op == CKPT_CPT) {
+ h->sock.flags = sk->sk_flags;
+ h->socket.flags = sk->sk_socket->flags;
+ } else {
+ int ret;
+ mm_segment_t old_fs;
+
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+ ret = sock_restore_flags(sk->sk_socket, h);
+ set_fs(old_fs);
+ if (ret)
+ return ret;
+ }
+
+ if ((h->socket.state == SS_CONNECTED) &&
+ (h->sock.state != TCP_ESTABLISHED)) {
+ ckpt_err(ctx, -EINVAL, "sock/et in inconsistent state: %i/%i",
+ h->socket.state, h->sock.state);
+ return -EINVAL;
+ } else if ((h->sock.state < TCP_ESTABLISHED) ||
+ (h->sock.state >= TCP_MAX_STATES)) {
+ ckpt_err(ctx, -EINVAL,
+ "sock in invalid state: %i", h->sock.state);
+ return -EINVAL;
+ } else if (h->socket.state > SS_DISCONNECTING) {
+ ckpt_err(ctx, -EINVAL, "socket in invalid state: %i",
+ h->socket.state);
+ return -EINVAL;
+ }
+
+ if (op == CKPT_RST)
+ return sock_cptrst_verify(h);
+ else
+ return 0;
+}
+
+static int __do_sock_checkpoint(struct ckpt_ctx *ctx, struct sock *sk)
+{
+ struct socket *sock = sk->sk_socket;
+ struct ckpt_hdr_socket *h;
+ int ret;
+
+ if (!sock->ops->checkpoint) {
+ ckpt_err(ctx, -ENOSYS, "%(T)%(V)%(P)socket: proto_ops\n",
+ sock->ops, sock);
+ return -ENOSYS;
+ }
+
+ h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_SOCKET);
+ if (!h)
+ return -ENOMEM;
+
+ /* part I: common to all sockets */
+ ret = sock_cptrst(ctx, sk, h, CKPT_CPT);
+ if (ret < 0)
+ goto out;
+
+ ret = ckpt_write_obj(ctx, (struct ckpt_hdr *) h);
+ if (ret < 0)
+ goto out;
+
+ /* part II: per socket type state */
+ ret = sock->ops->checkpoint(ctx, sock);
+ if (ret < 0)
+ goto out;
+
+ /* part III: socket buffers */
+ if ((sk->sk_state != TCP_LISTEN) && (!sock_flag(sk, SOCK_DEAD)))
+ ret = sock_defer_write_buffers(ctx, sk);
+ out:
+ ckpt_hdr_put(ctx, h);
+ return ret;
+}
+
+static int checkpoint_sock(struct ckpt_ctx *ctx, void *ptr)
+{
+ struct sock *sk = ptr;
+ struct socket *sock;
+ int ret;
+
+ if (sk->sk_socket)
+ return __do_sock_checkpoint(ctx, sk);
+
+ /* Temporarily adopt this orphan socket */
+ ret = sock_create(sk->sk_family, sk->sk_type, 0, &sock);
+ if (ret < 0)
+ return ret;
+ sock_graft(sk, sock);
+
+ ret = __do_sock_checkpoint(ctx, sk);
+
+ sock_orphan(sk);
+ sock->sk = NULL;
+ sock_release(sock);
+
+ return ret;
+}
+
+int sock_file_checkpoint(struct ckpt_ctx *ctx, struct file *file)
+{
+ struct ckpt_hdr_file_socket *h;
+ struct socket *sock = file->private_data;
+ struct sock *sk = sock->sk;
+ int ret;
+
+ h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_FILE);
+ if (!h)
+ return -ENOMEM;
+
+ h->common.f_type = CKPT_FILE_SOCKET;
+
+ h->sock_objref = checkpoint_obj(ctx, sk, CKPT_OBJ_SOCK);
+ if (h->sock_objref < 0) {
+ ret = h->sock_objref;
+ goto out;
+ }
+
+ ret = checkpoint_file_common(ctx, file, &h->common);
+ if (ret < 0)
+ goto out;
+
+ ret = ckpt_write_obj(ctx, (struct ckpt_hdr *) h);
+ out:
+ ckpt_hdr_put(ctx, h);
+ return ret;
+}
+
+static int sock_collect_skbs(struct ckpt_ctx *ctx, struct sk_buff_head *queue)
+{
+ struct sk_buff_head tmpq;
+ struct sk_buff *skb;
+ int ret = 0;
+ int bytes;
+
+ skb_queue_head_init(&tmpq);
+
+ ret = sock_copy_buffers(queue, &tmpq, &bytes);
+ if (ret < 0)
+ return ret;
+
+ skb_queue_walk(&tmpq, skb) {
+ /* Socket buffers do not maintain a ref count on their
+ * owning sock because they're counted in sock_wmem_alloc.
+ * So, we only need to collect sockets from the queue that
+ * won't be collected any other way (i.e. DEAD sockets that
+ * are hanging around only because they're waiting for us
+ * to process their skb.
+ */
+
+ if (!ckpt_obj_lookup(ctx, skb->sk, CKPT_OBJ_SOCK) &&
+ sock_flag(skb->sk, SOCK_DEAD)) {
+ ret = ckpt_obj_collect(ctx, skb->sk, CKPT_OBJ_SOCK);
+ if (ret < 0)
+ break;
+ }
+ }
+
+ __skb_queue_purge(&tmpq);
+
+ return ret;
+}
+
+int sock_file_collect(struct ckpt_ctx *ctx, struct file *file)
+{
+ struct socket *sock = file->private_data;
+ struct sock *sk = sock->sk;
+ int ret;
+
+ ret = sock_collect_skbs(ctx, &sk->sk_write_queue);
+ if (ret < 0)
+ return ret;
+
+ ret = sock_collect_skbs(ctx, &sk->sk_receive_queue);
+ if (ret < 0)
+ return ret;
+
+ ret = ckpt_obj_collect(ctx, sk, CKPT_OBJ_SOCK);
+ if (ret < 0)
+ return ret;
+
+ if (sock->ops->collect)
+ ret = sock->ops->collect(ctx, sock);
+
+ return ret;
+}
+
+static void *restore_sock(struct ckpt_ctx *ctx)
+{
+ struct ckpt_hdr_socket *h;
+ struct socket *sock;
+ int ret;
+
+ h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_SOCKET);
+ if (IS_ERR(h))
+ return ERR_PTR(PTR_ERR(h));
+
+ /* silently clear flags, e.g. SOCK_NONBLOCK or SOCK_CLOEXEC */
+ h->sock.type &= SOCK_TYPE_MASK;
+
+ ret = sock_create(h->sock_common.family, h->sock.type,
+ h->sock.protocol, &sock);
+ if (ret < 0)
+ goto err;
+
+ if (!sock->ops->restore) {
+ ret = -EINVAL;
+ ckpt_err(ctx, ret, "proto_ops lacks restore %pS\n", sock->ops);
+ goto err;
+ }
+
+ /*
+ * part II: per socket type state
+ * (also takes care of part III: socket buffer)
+ */
+ ret = sock->ops->restore(ctx, sock, h);
+ if (ret < 0)
+ goto err;
+
+ /* part I: common to all sockets */
+ ret = sock_cptrst(ctx, sock->sk, h, CKPT_RST);
+ if (ret < 0)
+ goto err;
+
+ ckpt_hdr_put(ctx, h);
+ return sock->sk;
+ err:
+ ckpt_hdr_put(ctx, h);
+ sock_release(sock);
+ return ERR_PTR(ret);
+}
+
+struct file *sock_file_restore(struct ckpt_ctx *ctx, struct ckpt_hdr_file *ptr)
+{
+ struct ckpt_hdr_file_socket *h = (struct ckpt_hdr_file_socket *)ptr;
+ struct sock *sk;
+ struct file *file;
+ int fd, ret;
+
+ if (ptr->h.type != CKPT_HDR_FILE || ptr->f_type != CKPT_FILE_SOCKET)
+ return ERR_PTR(-EINVAL);
+
+ sk = ckpt_obj_fetch(ctx, h->sock_objref, CKPT_OBJ_SOCK);
+ if (IS_ERR(sk))
+ return ERR_PTR(PTR_ERR(sk));
+
+ fd = sock_alloc_file(sk->sk_socket, &file, O_RDWR);
+ if (fd < 0)
+ return ERR_PTR(fd);
+ put_unused_fd(fd); /* We'll let the checkpoint code re-allocate this */
+
+ /* Since objhash assumes the initial reference for a socket,
+ * we bump it here for this descriptor, unlike other places in
+ * the socket code which assume the descriptor is the owner.
+ */
+ sock_hold(sk);
+
+ ret = restore_file_common(ctx, file, ptr);
+ if (ret < 0) {
+ fput(file);
+ return ERR_PTR(ret);
+ }
+
+ return file;
+}
+
+/*
+ * sock-related checkpoint objects
+ */
+
+static int obj_sock_grab(void *ptr)
+{
+ sock_hold((struct sock *) ptr);
+ return 0;
+}
+
+static void obj_sock_drop(void *ptr, int lastref)
+{
+ struct sock *sk = (struct sock *) ptr;
+
+ /*
+ * Sockets created during restart are graft()ed, i.e. have a
+ * valid @sk->sk_socket. Because only an fput() results in the
+ * necessary sock_release(), we may leak the struct socket of
+ * sockets that were not attached to a file. Therefore, if
+ * @lastref is set, we hereby invoke sock_release() on sockets
+ * that we have put into the objhash but were never attached
+ * to a file.
+ */
+ if (lastref && sk->sk_socket && !sk->sk_socket->file) {
+ struct socket *sock = sk->sk_socket;
+ sock_orphan(sk);
+ sock->sk = NULL;
+ sock_release(sock);
+ }
+
+ sock_put((struct sock *) ptr);
+}
+
+static int obj_sock_users(void *ptr)
+{
+ return atomic_read(&((struct sock *) ptr)->sk_refcnt);
+}
+
+/* sock object */
+static const struct ckpt_obj_ops ckpt_obj_sock_ops = {
+ .obj_name = "SOCKET",
+ .obj_type = CKPT_OBJ_SOCK,
+ .ref_drop = obj_sock_drop,
+ .ref_grab = obj_sock_grab,
+ .ref_users = obj_sock_users,
+ .checkpoint = checkpoint_sock,
+ .restore = restore_sock,
+};
+
+static int __init checkpoint_register_sock(void)
+{
+ return register_checkpoint_obj(&ckpt_obj_sock_ops);
+}
+module_init(checkpoint_register_sock);
diff --git a/net/socket.c b/net/socket.c
index b9f421b..da2864f 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -148,6 +148,10 @@ static const struct file_operations socket_file_ops = {
.sendpage = sock_sendpage,
.splice_write = generic_splice_sendpage,
.splice_read = sock_splice_read,
+#ifdef CONFIG_CHECKPOINT
+ .checkpoint = sock_file_checkpoint,
+ .collect = sock_file_collect,
+#endif
};
/*
@@ -343,7 +347,7 @@ static const struct dentry_operations sockfs_dentry_operations = {
* but we take care of internal coherence yet.
*/
-static int sock_alloc_file(struct socket *sock, struct file **f, int flags)
+int sock_alloc_file(struct socket *sock, struct file **f, int flags)
{
struct qstr name = { .name = "" };
struct path path;
diff --git a/net/unix/Makefile b/net/unix/Makefile
index b852a2b..fbff1e6 100644
--- a/net/unix/Makefile
+++ b/net/unix/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_UNIX) += unix.o
unix-y := af_unix.o garbage.o
unix-$(CONFIG_SYSCTL) += sysctl_net_unix.o
+unix-$(CONFIG_CHECKPOINT) += checkpoint.o
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 3d9122e..a7d0cff 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -523,6 +523,9 @@ static const struct proto_ops unix_stream_ops = {
.recvmsg = unix_stream_recvmsg,
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
+ .checkpoint = unix_checkpoint,
+ .restore = unix_restore,
+ .collect = unix_collect,
};
static const struct proto_ops unix_dgram_ops = {
@@ -544,6 +547,9 @@ static const struct proto_ops unix_dgram_ops = {
.recvmsg = unix_dgram_recvmsg,
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
+ .checkpoint = unix_checkpoint,
+ .restore = unix_restore,
+ .collect = unix_collect,
};
static const struct proto_ops unix_seqpacket_ops = {
@@ -565,6 +571,9 @@ static const struct proto_ops unix_seqpacket_ops = {
.recvmsg = unix_dgram_recvmsg,
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
+ .checkpoint = unix_checkpoint,
+ .restore = unix_restore,
+ .collect = unix_collect,
};
static struct proto unix_proto = {
diff --git a/net/unix/checkpoint.c b/net/unix/checkpoint.c
new file mode 100644
index 0000000..c90a497
--- /dev/null
+++ b/net/unix/checkpoint.c
@@ -0,0 +1,646 @@
+#include <linux/namei.h>
+#include <linux/file.h>
+#include <linux/fs_struct.h>
+#include <linux/deferqueue.h>
+#include <linux/checkpoint.h>
+#include <linux/user.h>
+#include <net/af_unix.h>
+#include <net/tcp_states.h>
+
+struct dq_join {
+ struct ckpt_ctx *ctx;
+ int src_objref;
+ int dst_objref;
+};
+
+struct dq_buffers {
+ struct ckpt_ctx *ctx;
+ int sk_objref; /* objref of the socket these buffers belong to */
+};
+
+#define UNIX_ADDR_EMPTY(a) (a <= sizeof(short))
+
+static inline int unix_need_cwd(struct sockaddr_un *addr, unsigned long len)
+{
+ return (!UNIX_ADDR_EMPTY(len)) &&
+ addr->sun_path[0] &&
+ (addr->sun_path[0] != '/');
+}
+
+static int unix_join(struct sock *src, struct sock *dst)
+{
+ if (unix_sk(src)->peer != NULL)
+ return 0; /* We're second */
+
+ sock_hold(dst);
+ unix_sk(src)->peer = dst;
+
+ return 0;
+
+}
+
+static int unix_deferred_join(void *data)
+{
+ struct dq_join *dq = (struct dq_join *)data;
+ struct ckpt_ctx *ctx = dq->ctx;
+ struct sock *src;
+ struct sock *dst;
+
+ src = ckpt_obj_fetch(ctx, dq->src_objref, CKPT_OBJ_SOCK);
+ if (!src) {
+ ckpt_err(ctx, -EINVAL, "%(O)Bad src sock\n", dq->src_objref);
+ return -EINVAL;
+ }
+
+ dst = ckpt_obj_fetch(ctx, dq->dst_objref, CKPT_OBJ_SOCK);
+ if (!dst) {
+ ckpt_err(ctx, -EINVAL, "%(O)Bad dst sock\n", dq->dst_objref);
+ return -EINVAL;
+ }
+
+ return unix_join(src, dst);
+}
+
+static int unix_defer_join(struct ckpt_ctx *ctx,
+ int src_objref,
+ int dst_objref)
+{
+ struct dq_join dq;
+
+ dq.ctx = ctx;
+ dq.src_objref = src_objref;
+ dq.dst_objref = dst_objref;
+
+ /* NB: This is safe to do inside deferqueue_run() since it uses
+ * list_for_each_safe()
+ */
+ return deferqueue_add(ctx->files_deferq, &dq, sizeof(dq),
+ unix_deferred_join, NULL);
+}
+
+static int unix_write_cwd(struct ckpt_ctx *ctx,
+ struct sock *sk, const char *sockpath)
+{
+ struct path path;
+ char *buf;
+ char *fqpath;
+ int offset;
+ int len = PATH_MAX;
+ int ret = -ENOENT;
+
+ buf = kmalloc(len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ path.dentry = unix_sk(sk)->dentry;
+ path.mnt = unix_sk(sk)->mnt;
+
+ fqpath = ckpt_fill_fname(&path, &ctx->root_fs_path, buf, &len);
+ if (IS_ERR(fqpath)) {
+ ret = PTR_ERR(fqpath);
+ goto out;
+ }
+
+ offset = strlen(fqpath) - strlen(sockpath);
+ if (offset <= 0) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ fqpath[offset] = '\0';
+
+ ckpt_debug("writing socket directory: %s\n", fqpath);
+ ret = ckpt_write_string(ctx, fqpath, offset + 1);
+ out:
+ kfree(buf);
+ return ret;
+}
+
+int unix_checkpoint(struct ckpt_ctx *ctx, struct socket *sock)
+{
+ struct unix_sock *sk = unix_sk(sock->sk);
+ struct ckpt_hdr_socket_unix *un;
+ int new;
+ int ret = -ENOMEM;
+
+ if ((sock->sk->sk_state == TCP_LISTEN) &&
+ !skb_queue_empty(&sock->sk->sk_receive_queue)) {
+ ckpt_err(ctx, -EBUSY,
+ "%(T)%(E)%(P)af_unix: listen with pending peers\n",
+ sock);
+ return -EBUSY;
+ }
+
+ un = ckpt_hdr_get_type(ctx, sizeof(*un), CKPT_HDR_SOCKET_UNIX);
+ if (!un)
+ return -EINVAL;
+
+ ret = ckpt_sock_getnames(ctx, sock,
+ (struct sockaddr *)&un->laddr, &un->laddr_len,
+ (struct sockaddr *)&un->raddr, &un->raddr_len);
+ if (ret)
+ goto out;
+
+ if (sk->dentry && (sk->dentry->d_inode->i_nlink > 0))
+ un->flags |= CKPT_UNIX_LINKED;
+
+ un->this = ckpt_obj_lookup_add(ctx, sk, CKPT_OBJ_SOCK, &new);
+ if (un->this < 0)
+ goto out;
+
+ if (sk->peer)
+ un->peer = checkpoint_obj(ctx, sk->peer, CKPT_OBJ_SOCK);
+ else
+ un->peer = 0;
+
+ if (un->peer < 0) {
+ ret = un->peer;
+ goto out;
+ }
+
+ un->peercred_uid = sock->sk->sk_peercred.uid;
+ un->peercred_gid = sock->sk->sk_peercred.gid;
+
+ ret = ckpt_write_obj(ctx, (struct ckpt_hdr *) un);
+ if (ret < 0)
+ goto out;
+
+ if (unix_need_cwd(&un->laddr, un->laddr_len))
+ ret = unix_write_cwd(ctx, sock->sk, un->laddr.sun_path);
+ out:
+ ckpt_hdr_put(ctx, un);
+
+ return ret;
+}
+
+int unix_collect(struct ckpt_ctx *ctx, struct socket *sock)
+{
+ struct unix_sock *sk = unix_sk(sock->sk);
+ int ret;
+
+ ret = ckpt_obj_collect(ctx, sock->sk, CKPT_OBJ_SOCK);
+ if (ret < 0)
+ return ret;
+
+ if (sk->peer)
+ ret = ckpt_obj_collect(ctx, sk->peer, CKPT_OBJ_SOCK);
+
+ return 0;
+}
+
+static int sock_read_buffer_sendmsg(struct ckpt_ctx *ctx,
+ struct sockaddr *addr,
+ unsigned int addrlen)
+{
+ struct ckpt_hdr_socket_buffer *h;
+ struct sock *sk;
+ struct msghdr msg;
+ struct kvec kvec;
+ uint8_t sock_shutdown;
+ uint8_t peer_shutdown = 0;
+ void *buf = NULL;
+ int sndbuf;
+ int ret;
+
+ memset(&msg, 0, sizeof(msg));
+
+ h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_SOCKET_BUFFER);
+ if (IS_ERR(h))
+ return PTR_ERR(h);
+
+ ret = -EINVAL;
+ if (h->lin_len > SKB_MAX_ALLOC) {
+ ckpt_err(ctx, ret, "socket buffer too big (%u > %lu)\n",
+ h->lin_len, SKB_MAX_ALLOC);
+ goto out;
+ } else if (h->nr_frags != 0) {
+ ckpt_err(ctx, ret, "unix socket claims to have fragments\n");
+ goto out;
+ }
+
+ buf = kmalloc(h->lin_len, GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ kvec.iov_len = h->lin_len;
+ kvec.iov_base = buf;
+ ret = _ckpt_read_obj_type(ctx, kvec.iov_base,
+ h->lin_len, CKPT_HDR_BUFFER);
+ ckpt_debug("read unix socket buffer %u: %i\n", h->lin_len, ret);
+ if (ret < h->lin_len) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ sk = ckpt_obj_fetch(ctx, h->sk_objref, CKPT_OBJ_SOCK);
+ if (IS_ERR(sk)) {
+ ret = PTR_ERR(sk);
+ goto out;
+ }
+
+ /* If we don't have a destination or a peer and we know the
+ * destination of this skb, then we must need to join with our
+ * peer
+ */
+ if (!addrlen && !unix_sk(sk)->peer) {
+ struct sock *pr;
+ pr = ckpt_obj_fetch(ctx, h->pr_objref, CKPT_OBJ_SOCK);
+ if (IS_ERR(pr)) {
+ ret = PTR_ERR(pr);
+ ckpt_err(ctx, ret, "Failed to fetch peer\n");
+ goto out;
+ }
+ ret = unix_join(sk, pr);
+ if (ret < 0) {
+ ckpt_err(ctx, ret, "Failed to join sockets\n");
+ goto out;
+ }
+ }
+
+ msg.msg_name = addr;
+ msg.msg_namelen = addrlen;
+
+ /* If peer is shutdown, unshutdown it for this process */
+ sock_shutdown = sk->sk_shutdown;
+ sk->sk_shutdown &= ~SHUTDOWN_MASK;
+
+ /* Unshutdown peer too, if necessary */
+ if (unix_sk(sk)->peer) {
+ peer_shutdown = unix_sk(sk)->peer->sk_shutdown;
+ unix_sk(sk)->peer->sk_shutdown &= ~SHUTDOWN_MASK;
+ }
+
+ /* Make sure there's room in the send buffer: Worst case, we
+ * give them the benefit of the doubt and set the buffer limit
+ * to the system default. This should cover the case where
+ * the user set the limit low after loading up the buffer.
+ *
+ * However, if there isn't room in the buffer and the system
+ * default won't accommodate them either, then increase the
+ * limit as needed, only if they have CAP_NET_ADMIN.
+ */
+ sndbuf = sk->sk_sndbuf;
+ if (((sk->sk_sndbuf - atomic_read(&sk->sk_wmem_alloc)) < h->lin_len) &&
+ (h->lin_len > sysctl_wmem_max) &&
+ capable(CAP_NET_ADMIN))
+ sk->sk_sndbuf += h->lin_len;
+ else
+ sk->sk_sndbuf = sysctl_wmem_max;
+
+ ret = kernel_sendmsg(sk->sk_socket, &msg, &kvec, 1, h->lin_len);
+ ckpt_debug("kernel_sendmsg(%i,%u): %i\n",
+ h->sk_objref, h->lin_len, ret);
+ if ((ret > 0) && (ret != h->lin_len))
+ ret = -ENOMEM;
+
+ sk->sk_sndbuf = sndbuf;
+ sk->sk_shutdown = sock_shutdown;
+ if (peer_shutdown)
+ unix_sk(sk)->peer->sk_shutdown = peer_shutdown;
+ out:
+ ckpt_hdr_put(ctx, h);
+ kfree(buf);
+ return ret;
+}
+
+static int unix_read_buffers(struct ckpt_ctx *ctx,
+ struct sockaddr *addr,
+ unsigned int addrlen)
+{
+ struct ckpt_hdr_socket_queue *h;
+ int ret = 0;
+ int i;
+
+ h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_SOCKET_QUEUE);
+ if (IS_ERR(h))
+ return PTR_ERR(h);
+
+ for (i = 0; i < h->skb_count; i++) {
+ ret = sock_read_buffer_sendmsg(ctx, addr, addrlen);
+ ckpt_debug("read_buffer_sendmsg(%i): %i\n", i, ret);
+ if (ret < 0)
+ goto out;
+
+ if (ret > h->total_bytes) {
+ ret = -EINVAL;
+ ckpt_err(ctx, ret, "Buffers exceeded claim");
+ goto out;
+ }
+
+ h->total_bytes -= ret;
+ }
+
+ ret = h->skb_count;
+ out:
+ ckpt_hdr_put(ctx, h);
+ return ret;
+}
+
+static int unix_deferred_restore_buffers(void *data)
+{
+ struct dq_buffers *dq = (struct dq_buffers *)data;
+ struct ckpt_ctx *ctx = dq->ctx;
+ struct sock *sk;
+ struct sockaddr *addr = NULL;
+ unsigned int addrlen = 0;
+ int ret;
+
+ sk = ckpt_obj_fetch(ctx, dq->sk_objref, CKPT_OBJ_SOCK);
+ if (!sk) {
+ ckpt_err(ctx, -EINVAL, "%(O) missing sock\n", dq->sk_objref);
+ return -EINVAL;
+ }
+
+ if ((sk->sk_type == SOCK_DGRAM) && (unix_sk(sk)->addr != NULL)) {
+ addr = (struct sockaddr *)&unix_sk(sk)->addr->name;
+ addrlen = unix_sk(sk)->addr->len;
+ }
+
+ ret = unix_read_buffers(ctx, addr, addrlen);
+ ckpt_debug("read recv buffers: %i\n", ret);
+ if (ret < 0)
+ return ret;
+
+ ret = unix_read_buffers(ctx, addr, addrlen);
+ ckpt_debug("read send buffers: %i\n", ret);
+ if (ret > 0)
+ ret = -EINVAL; /* No send buffers for UNIX sockets */
+
+ return ret;
+}
+
+static int unix_defer_restore_buffers(struct ckpt_ctx *ctx, int sk_objref)
+{
+ struct dq_buffers dq;
+
+ dq.ctx = ctx;
+ dq.sk_objref = sk_objref;
+
+ /* NB: This is safe to do inside deferqueue_run() since it uses
+ * list_for_each_safe()
+ */
+ return deferqueue_add(ctx->files_deferq, &dq, sizeof(dq),
+ unix_deferred_restore_buffers, NULL);
+}
+
+static struct unix_address *unix_makeaddr(struct sockaddr_un *sun_addr,
+ unsigned len)
+{
+ struct unix_address *addr;
+
+ if (len > sizeof(struct sockaddr_un))
+ return ERR_PTR(-EINVAL);
+
+ addr = kmalloc(sizeof(*addr) + len, GFP_KERNEL);
+ if (!addr)
+ return ERR_PTR(-ENOMEM);
+
+ memcpy(addr->name, sun_addr, len);
+ addr->len = len;
+ atomic_set(&addr->refcnt, 1);
+
+ return addr;
+}
+
+static int unix_restore_connected(struct ckpt_ctx *ctx,
+ struct ckpt_hdr_socket *h,
+ struct ckpt_hdr_socket_unix *un,
+ struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr *addr = NULL;
+ unsigned long flags = h->sock.flags;
+ unsigned int addrlen = 0;
+ int dead = test_bit(SOCK_DEAD, &flags);
+ int ret = 0;
+
+
+ if (un->peer == 0) {
+ /* These get propagated to the msghdr, so only set them
+ * if we're not connected to a peer, else we'll get an error
+ * when we sendmsg()
+ */
+ addr = (struct sockaddr *)&un->laddr;
+ addrlen = un->laddr_len;
+ }
+
+ sk->sk_peercred.pid = task_tgid_vnr(current);
+
+ if (may_setuid(ctx->realcred->user->user_ns, un->peercred_uid) &&
+ may_setgid(un->peercred_gid)) {
+ sk->sk_peercred.uid = un->peercred_uid;
+ sk->sk_peercred.gid = un->peercred_gid;
+ } else {
+ ckpt_err(ctx, -EPERM, "peercred %i:%i would require setuid",
+ un->peercred_uid, un->peercred_gid);
+ return -EPERM;
+ }
+
+ if (!dead && (un->peer > 0)) {
+ ret = unix_defer_join(ctx, un->this, un->peer);
+ ckpt_debug("unix_defer_join: %i\n", ret);
+ }
+
+ if (!dead && !ret)
+ ret = unix_defer_restore_buffers(ctx, un->this);
+
+ return ret;
+}
+
+static int unix_unlink(const char *name)
+{
+ struct path spath;
+ struct path ppath;
+ int ret;
+
+ ret = kern_path(name, 0, &spath);
+ if (ret)
+ return ret;
+
+ ret = kern_path(name, LOOKUP_PARENT, &ppath);
+ if (ret)
+ goto out_s;
+
+ if (!spath.dentry) {
+ ckpt_debug("No dentry found for %s\n", name);
+ ret = -ENOENT;
+ goto out_p;
+ }
+
+ if (!ppath.dentry || !ppath.dentry->d_inode) {
+ ckpt_debug("No inode for parent of %s\n", name);
+ ret = -ENOENT;
+ goto out_p;
+ }
+
+ ret = vfs_unlink(ppath.dentry->d_inode, spath.dentry);
+ out_p:
+ path_put(&ppath);
+ out_s:
+ path_put(&spath);
+
+ return ret;
+}
+
+/* Call bind() for socket, optionally changing (temporarily) to @path first
+ * if non-NULL
+ */
+static int unix_chdir_and_bind(struct socket *sock,
+ const char *path,
+ struct sockaddr *addr,
+ unsigned long addrlen)
+{
+ struct sockaddr_un *un = (struct sockaddr_un *)addr;
+ struct path cur = { .mnt = NULL, .dentry = NULL };
+ struct path dir = { .mnt = NULL, .dentry = NULL };
+ int ret;
+
+ if (path) {
+ ckpt_debug("switching to cwd %s for unix bind", path);
+
+ ret = kern_path(path, 0, &dir);
+ if (ret)
+ return ret;
+
+ ret = inode_permission(dir.dentry->d_inode,
+ MAY_EXEC | MAY_ACCESS);
+ if (ret)
+ goto out;
+
+ write_lock(¤t->fs->lock);
+ cur = current->fs->pwd;
+ current->fs->pwd = dir;
+ write_unlock(¤t->fs->lock);
+ }
+
+ ret = unix_unlink(un->sun_path);
+ ckpt_debug("unlink(%s): %i\n", un->sun_path, ret);
+ if ((ret == 0) || (ret == -ENOENT))
+ ret = sock_bind(sock, addr, addrlen);
+
+ if (path) {
+ write_lock(¤t->fs->lock);
+ current->fs->pwd = cur;
+ write_unlock(¤t->fs->lock);
+ }
+ out:
+ if (path)
+ path_put(&dir);
+
+ return ret;
+}
+
+static int unix_fakebind(struct socket *sock,
+ struct sockaddr_un *addr, unsigned long len)
+{
+ struct unix_address *uaddr;
+
+ uaddr = unix_makeaddr(addr, len);
+ if (IS_ERR(uaddr))
+ return PTR_ERR(uaddr);
+
+ unix_sk(sock->sk)->addr = uaddr;
+
+ return 0;
+}
+
+static int unix_restore_bind(struct ckpt_hdr_socket *h,
+ struct ckpt_hdr_socket_unix *un,
+ struct socket *sock,
+ const char *path)
+{
+ struct sockaddr *addr = (struct sockaddr *)&un->laddr;
+ unsigned long len = un->laddr_len;
+ unsigned long flags = h->sock.flags;
+ int dead = test_bit(SOCK_DEAD, &flags);
+
+ if (dead)
+ return unix_fakebind(sock, &un->laddr, len);
+ else if (!un->laddr.sun_path[0])
+ return sock_bind(sock, addr, len);
+ else if (!(un->flags & CKPT_UNIX_LINKED))
+ return unix_fakebind(sock, &un->laddr, len);
+ else
+ return unix_chdir_and_bind(sock, path, addr, len);
+}
+
+/* Some easy pre-flight checks before we get underway */
+static int unix_precheck(struct socket *sock, struct ckpt_hdr_socket *h)
+{
+ struct net *net = sock_net(sock->sk);
+ unsigned long sk_flags = h->sock.flags;
+
+ if ((h->socket.state == SS_CONNECTING) ||
+ (h->socket.state == SS_DISCONNECTING) ||
+ (h->socket.state == SS_FREE)) {
+ ckpt_debug("AF_UNIX socket can't be SS_(DIS)CONNECTING");
+ return -EINVAL;
+ }
+
+ /* AF_UNIX overloads the backlog setting to define the maximum
+ * queue length for DGRAM sockets. Make sure we don't let the
+ * caller exceed that value on restart.
+ */
+ if ((h->sock.type == SOCK_DGRAM) &&
+ (h->sock.backlog > net->unx.sysctl_max_dgram_qlen)) {
+ ckpt_debug("DGRAM backlog of %i exceeds system max of %i\n",
+ h->sock.backlog, net->unx.sysctl_max_dgram_qlen);
+ return -EINVAL;
+ }
+
+ if (test_bit(SOCK_USE_WRITE_QUEUE, &sk_flags)) {
+ ckpt_debug("AF_UNIX socket has SOCK_USE_WRITE_QUEUE set");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int unix_restore(struct ckpt_ctx *ctx, struct socket *sock,
+ struct ckpt_hdr_socket *h)
+
+{
+ struct ckpt_hdr_socket_unix *un;
+ int ret = -EINVAL;
+ char *cwd = NULL;
+
+ ret = unix_precheck(sock, h);
+ if (ret)
+ return ret;
+
+ un = ckpt_read_obj_type(ctx, sizeof(*un), CKPT_HDR_SOCKET_UNIX);
+ if (IS_ERR(un))
+ return PTR_ERR(un);
+
+ if (un->peer < 0)
+ goto out;
+
+ if (unix_need_cwd(&un->laddr, un->laddr_len)) {
+ cwd = ckpt_read_string(ctx, PATH_MAX);
+ if (IS_ERR(cwd)) {
+ ret = PTR_ERR(cwd);
+ goto out;
+ }
+ }
+
+ if ((h->sock.state != TCP_ESTABLISHED) &&
+ !UNIX_ADDR_EMPTY(un->laddr_len)) {
+ ret = unix_restore_bind(h, un, sock, cwd);
+ if (ret)
+ goto out;
+ }
+
+ if ((h->sock.state == TCP_ESTABLISHED) || (h->sock.state == TCP_CLOSE))
+ ret = unix_restore_connected(ctx, h, un, sock);
+ else if (h->sock.state == TCP_LISTEN)
+ ret = sock->ops->listen(sock, h->sock.backlog);
+ else
+ ckpt_err(ctx, ret, "bad af_unix state %i\n", h->sock.state);
+
+ out:
+ ckpt_hdr_put(ctx, un);
+ kfree(cwd);
+ return ret;
+}
--
1.6.3.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v21 074/100] c/r: add support for listening INET sockets (v2)
[not found] <1272723382-19470-1-git-send-email-orenl@cs.columbia.edu>
` (4 preceding siblings ...)
2010-05-01 14:15 ` [PATCH v21 073/100] c/r: Add AF_UNIX support (v12) Oren Laadan
@ 2010-05-01 14:15 ` Oren Laadan
2010-05-01 14:15 ` [PATCH v21 075/100] c/r: add support for connected INET sockets (v5) Oren Laadan
` (7 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Oren Laadan @ 2010-05-01 14:15 UTC (permalink / raw)
To: Andrew Morton
Cc: containers, linux-kernel, Serge Hallyn, Matt Helsley,
Pavel Emelyanov, Dan Smith, netdev, Andrew Morton
From: Dan Smith <danms@us.ibm.com>
This is an incremental step towards supporting checkpoint/restart on
AF_INET sockets. In this scenario, any sockets that were in TCP_LISTEN
state are restored as they were. Any that were connected are forced to
TCP_CLOSE. This should cover a range of use cases that involve
applications that are tolerant of such an interruption.
Changelog [v21]:
- Do not include checkpoint_hdr.h explicitly
Changelog [v19-rc1]:
- [Matt Helsley] Add cpp definitions for enums
Changes in v2:
- Fix whitespace
- Fix return in inet_checkpoint() on failed ckpt_hdr_get_type()
- Fix garbage free on error path of inet_read_buffer()
- Fix unnecessary ret=0 in inet_read_buffers()
- Add inet_precheck() (like unix) to validate the address lengths (and
more later)
Cc: netdev@vger.kernel.org
Cc: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Dan Smith <danms@us.ibm.com>
Acked-by: Oren Laadan <orenl@cs.columbia.edu>
Acked-by: Serge E. Hallyn <serue@us.ibm.com>
Tested-by: Serge E. Hallyn <serue@us.ibm.com>
---
Documentation/checkpoint/readme.txt | 21 ++++
include/linux/checkpoint_hdr.h | 15 +++
include/net/inet_common.h | 13 +++
net/checkpoint.c | 9 ++
net/ipv4/Makefile | 1 +
net/ipv4/af_inet.c | 6 +
net/ipv4/checkpoint.c | 189 +++++++++++++++++++++++++++++++++++
7 files changed, 254 insertions(+), 0 deletions(-)
create mode 100644 net/ipv4/checkpoint.c
diff --git a/Documentation/checkpoint/readme.txt b/Documentation/checkpoint/readme.txt
index 4fa5560..2548bb4 100644
--- a/Documentation/checkpoint/readme.txt
+++ b/Documentation/checkpoint/readme.txt
@@ -344,6 +344,27 @@ we will be forced to more carefully review each of those features.
However, this can be controlled with a sysctl-variable.
+Sockets
+=======
+
+For AF_UNIX sockets, both endpoints must be within the checkpointed
+task set to maintain a connected state after restart. UNIX sockets
+that are in the process of passing a descriptor will cause the
+checkpoint to fail with -EBUSY indicating a transient state that
+cannot be checkpointed. Listening sockets with an unaccepted peer
+will also cause an -EBUSY result.
+
+AF_INET sockets with endpoints outside the checkpointed task set may
+remain open if care is taken to avoid TCP timeouts and resets.
+Careful use of a virtual IP address can help avoid emission of an RST
+to the non-checkpointed endpoint. If desired, the
+RESTART_SOCK_LISTENONLY flag may be passed to the restart syscall
+which will cause all connected AF_INET sockets to be closed during the
+restore process. Listening sockets will still be restored to their
+original state, which makes this mode a candidate for something like
+an HTTP server.
+
+
Kernel interfaces
=================
diff --git a/include/linux/checkpoint_hdr.h b/include/linux/checkpoint_hdr.h
index 2be2d2c..07934ee 100644
--- a/include/linux/checkpoint_hdr.h
+++ b/include/linux/checkpoint_hdr.h
@@ -14,6 +14,7 @@
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/un.h>
+#include <linux/in.h>
#ifndef CONFIG_CHECKPOINT
#error linux/checkpoint_hdr.h included directly (without CONFIG_CHECKPOINT)
@@ -21,10 +22,14 @@
#else /* __KERNEL__ */
+#else
+
#include <sys/types.h>
#include <linux/types.h>
#include <sys/socket.h>
#include <sys/un.h>
+#include <sys/un.h>
+#include <netinet/in.h>
#endif
@@ -159,6 +164,8 @@ enum {
#define CKPT_HDR_SOCKET_FRAG CKPT_HDR_SOCKET_FRAG
CKPT_HDR_SOCKET_UNIX,
#define CKPT_HDR_SOCKET_UNIX CKPT_HDR_SOCKET_UNIX
+ CKPT_HDR_SOCKET_INET,
+#define CKPT_HDR_SOCKET_INET CKPT_HDR_SOCKET_INET
CKPT_HDR_TAIL = 9001,
#define CKPT_HDR_TAIL CKPT_HDR_TAIL
@@ -577,6 +584,14 @@ struct ckpt_hdr_socket_unix {
struct sockaddr_un raddr;
} __attribute__ ((aligned(8)));
+struct ckpt_hdr_socket_inet {
+ struct ckpt_hdr h;
+ __u32 laddr_len;
+ __u32 raddr_len;
+ struct sockaddr_in laddr;
+ struct sockaddr_in raddr;
+} __attribute__((aligned(8)));
+
struct ckpt_hdr_file_socket {
struct ckpt_hdr_file common;
__s32 sock_objref;
diff --git a/include/net/inet_common.h b/include/net/inet_common.h
index 18c7732..bf04e6e 100644
--- a/include/net/inet_common.h
+++ b/include/net/inet_common.h
@@ -45,6 +45,19 @@ extern int inet_ctl_sock_create(struct sock **sk,
unsigned char protocol,
struct net *net);
+#ifdef CONFIG_CHECKPOINT
+struct ckpt_ctx;
+struct ckpt_hdr_socket;
+extern int inet_checkpoint(struct ckpt_ctx *ctx, struct socket *sock);
+extern int inet_collect(struct ckpt_ctx *ctx, struct socket *sock);
+extern int inet_restore(struct ckpt_ctx *cftx, struct socket *sock,
+ struct ckpt_hdr_socket *h);
+#else
+#define inet_checkpoint NULL
+#define inet_collect NULL
+#define inet_restore NULL
+#endif /* CONFIG_CHECKPOINT */
+
static inline void inet_ctl_sock_destroy(struct sock *sk)
{
sk_release_kernel(sk);
diff --git a/net/checkpoint.c b/net/checkpoint.c
index 9116d7a..d972044 100644
--- a/net/checkpoint.c
+++ b/net/checkpoint.c
@@ -930,6 +930,15 @@ static void *restore_sock(struct ckpt_ctx *ctx)
if (ret < 0)
goto err;
+ if ((h->sock_common.family == AF_INET) &&
+ (h->sock.state != TCP_LISTEN)) {
+ /* Temporary hack to enable restore of TCP_LISTEN sockets
+ * while forcing anything else to a closed state
+ */
+ sock->sk->sk_state = TCP_CLOSE;
+ sock->state = SS_UNCONNECTED;
+ }
+
ckpt_hdr_put(ctx, h);
return sock->sk;
err:
diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile
index 80ff87c..c00d8ce 100644
--- a/net/ipv4/Makefile
+++ b/net/ipv4/Makefile
@@ -49,6 +49,7 @@ obj-$(CONFIG_TCP_CONG_LP) += tcp_lp.o
obj-$(CONFIG_TCP_CONG_YEAH) += tcp_yeah.o
obj-$(CONFIG_TCP_CONG_ILLINOIS) += tcp_illinois.o
obj-$(CONFIG_NETLABEL) += cipso_ipv4.o
+obj-$(CONFIG_CHECKPOINT) += checkpoint.o
obj-$(CONFIG_XFRM) += xfrm4_policy.o xfrm4_state.o xfrm4_input.o \
xfrm4_output.o
diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
index f713574..8b7d3dd 100644
--- a/net/ipv4/af_inet.c
+++ b/net/ipv4/af_inet.c
@@ -876,6 +876,9 @@ const struct proto_ops inet_stream_ops = {
.mmap = sock_no_mmap,
.sendpage = tcp_sendpage,
.splice_read = tcp_splice_read,
+ .checkpoint = inet_checkpoint,
+ .restore = inet_restore,
+ .collect = inet_collect,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_sock_common_setsockopt,
.compat_getsockopt = compat_sock_common_getsockopt,
@@ -902,6 +905,9 @@ const struct proto_ops inet_dgram_ops = {
.recvmsg = sock_common_recvmsg,
.mmap = sock_no_mmap,
.sendpage = inet_sendpage,
+ .checkpoint = inet_checkpoint,
+ .restore = inet_restore,
+ .collect = inet_collect,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_sock_common_setsockopt,
.compat_getsockopt = compat_sock_common_getsockopt,
diff --git a/net/ipv4/checkpoint.c b/net/ipv4/checkpoint.c
new file mode 100644
index 0000000..0b62a15
--- /dev/null
+++ b/net/ipv4/checkpoint.c
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2009 IBM Corporation
+ *
+ * Author(s): Dan Smith <danms@us.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#include <linux/namei.h>
+#include <linux/tcp.h>
+#include <linux/in.h>
+#include <linux/deferqueue.h>
+#include <linux/checkpoint.h>
+#include <net/tcp_states.h>
+#include <net/tcp.h>
+
+struct dq_sock {
+ struct ckpt_ctx *ctx;
+ struct sock *sk;
+};
+
+struct dq_buffers {
+ struct ckpt_ctx *ctx;
+ struct sock *sk;
+};
+
+int inet_checkpoint(struct ckpt_ctx *ctx, struct socket *sock)
+{
+ struct ckpt_hdr_socket_inet *in;
+ int ret;
+
+ in = ckpt_hdr_get_type(ctx, sizeof(*in), CKPT_HDR_SOCKET_INET);
+ if (!in)
+ return -EINVAL;
+
+ ret = ckpt_sock_getnames(ctx, sock,
+ (struct sockaddr *)&in->laddr, &in->laddr_len,
+ (struct sockaddr *)&in->raddr, &in->raddr_len);
+ if (ret)
+ goto out;
+
+ ret = ckpt_write_obj(ctx, (struct ckpt_hdr *) in);
+ out:
+ ckpt_hdr_put(ctx, in);
+
+ return ret;
+}
+
+int inet_collect(struct ckpt_ctx *ctx, struct socket *sock)
+{
+ return ckpt_obj_collect(ctx, sock->sk, CKPT_OBJ_SOCK);
+}
+
+static int inet_read_buffer(struct ckpt_ctx *ctx, struct sk_buff_head *queue)
+{
+ struct sk_buff *skb = NULL;
+
+ skb = sock_restore_skb(ctx);
+ if (IS_ERR(skb))
+ return PTR_ERR(skb);
+
+ skb_queue_tail(queue, skb);
+ return skb->len;
+}
+
+static int inet_read_buffers(struct ckpt_ctx *ctx, struct sk_buff_head *queue)
+{
+ struct ckpt_hdr_socket_queue *h;
+ int ret = 0;
+ int i;
+
+ h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_SOCKET_QUEUE);
+ if (IS_ERR(h))
+ return PTR_ERR(h);
+
+ for (i = 0; i < h->skb_count; i++) {
+ ret = inet_read_buffer(ctx, queue);
+ ckpt_debug("read inet buffer %i: %i", i, ret);
+ if (ret < 0)
+ goto out;
+
+ if (ret > h->total_bytes) {
+ ret = -EINVAL;
+ ckpt_err(ctx, ret, "Buffers exceeded claim");
+ goto out;
+ }
+
+ h->total_bytes -= ret;
+ }
+
+ ret = h->skb_count;
+ out:
+ ckpt_hdr_put(ctx, h);
+
+ return ret;
+}
+
+static int inet_deferred_restore_buffers(void *data)
+{
+ struct dq_buffers *dq = (struct dq_buffers *)data;
+ struct ckpt_ctx *ctx = dq->ctx;
+ struct sock *sk = dq->sk;
+ int ret;
+
+ ret = inet_read_buffers(ctx, &sk->sk_receive_queue);
+ ckpt_debug("(R) inet_read_buffers: %i\n", ret);
+ if (ret < 0)
+ return ret;
+
+ ret = inet_read_buffers(ctx, &sk->sk_write_queue);
+ ckpt_debug("(W) inet_read_buffers: %i\n", ret);
+
+ return ret;
+}
+
+static int inet_defer_restore_buffers(struct ckpt_ctx *ctx, struct sock *sk)
+{
+ struct dq_buffers dq;
+
+ dq.ctx = ctx;
+ dq.sk = sk;
+
+ return deferqueue_add(ctx->files_deferq, &dq, sizeof(dq),
+ inet_deferred_restore_buffers, NULL);
+}
+
+static int inet_precheck(struct socket *sock, struct ckpt_hdr_socket_inet *in)
+{
+ if (in->laddr_len > sizeof(struct sockaddr_in)) {
+ ckpt_debug("laddr_len is too big\n");
+ return -EINVAL;
+ }
+
+ if (in->raddr_len > sizeof(struct sockaddr_in)) {
+ ckpt_debug("raddr_len is too big\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int inet_restore(struct ckpt_ctx *ctx,
+ struct socket *sock,
+ struct ckpt_hdr_socket *h)
+{
+ struct ckpt_hdr_socket_inet *in;
+ int ret = 0;
+
+ in = ckpt_read_obj_type(ctx, sizeof(*in), CKPT_HDR_SOCKET_INET);
+ if (IS_ERR(in))
+ return PTR_ERR(in);
+
+ ret = inet_precheck(sock, in);
+ if (ret < 0)
+ goto out;
+
+ /* Listening sockets and those that are closed but have a local
+ * address need to call bind()
+ */
+ if ((h->sock.state == TCP_LISTEN) ||
+ ((h->sock.state == TCP_CLOSE) && (in->laddr_len > 0))) {
+ sock->sk->sk_reuse = 2;
+ inet_sk(sock->sk)->freebind = 1;
+ ret = sock->ops->bind(sock,
+ (struct sockaddr *)&in->laddr,
+ in->laddr_len);
+ ckpt_debug("inet bind: %i\n", ret);
+ if (ret < 0)
+ goto out;
+
+ if (h->sock.state == TCP_LISTEN) {
+ ret = sock->ops->listen(sock, h->sock.backlog);
+ ckpt_debug("inet listen: %i\n", ret);
+ if (ret < 0)
+ goto out;
+ }
+ } else {
+ if (!sock_flag(sock->sk, SOCK_DEAD))
+ ret = inet_defer_restore_buffers(ctx, sock->sk);
+ }
+ out:
+ ckpt_hdr_put(ctx, in);
+
+ return ret;
+}
+
--
1.6.3.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v21 075/100] c/r: add support for connected INET sockets (v5)
[not found] <1272723382-19470-1-git-send-email-orenl@cs.columbia.edu>
` (5 preceding siblings ...)
2010-05-01 14:15 ` [PATCH v21 074/100] c/r: add support for listening INET sockets (v2) Oren Laadan
@ 2010-05-01 14:15 ` Oren Laadan
2010-05-01 14:16 ` [PATCH v21 093/100] c/r: Add checkpoint and collect hooks to net_device_ops Oren Laadan
` (6 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Oren Laadan @ 2010-05-01 14:15 UTC (permalink / raw)
To: Andrew Morton
Cc: containers, linux-kernel, Serge Hallyn, Matt Helsley,
Pavel Emelyanov, Dan Smith, netdev
From: Dan Smith <danms@us.ibm.com>
This patch adds basic support for C/R of open INET sockets. I think that
all the important bits of the TCP and ICSK socket structures is saved,
but I think there is still some additional IPv6 stuff that needs to be
handled.
With this patch applied, the following script can be used to demonstrate
the functionality:
https://lists.linux-foundation.org/pipermail/containers/2009-October/021239.html
It shows that this enables migration of a sendmail process with open
connections from one machine to another without dropping.
We probably need comments from the netdev people about the quality of
sanity checking we do on the values in the ckpt_hdr_socket_inet
structure on restart.
Note that this still doesn't address lingering sockets yet.
Changelog [v21]:
- [Dan Smith] Fix build when CONFIG_INET=n
Changelog [v19-rc3]:
- Rebase to kernel 2.6.33 (add 'inet_' prefix to some sk fields)
- Relax tcp.window_clamp value in INET restore
Changelog [v19-rc2]:
- Restore gso_type fields on sockets and buffers, so that they're
properly handled on incoming path. Use the proper value from the
socket (instead of storing that per-buffer) to avoid needing to
detect (e.g.) the user restore a UDP buffer into a TCP socket.
Changes in v5:
- Change ckpt_write_err() to ckpt_err()
Changes in v4:
- Use the new socket buffer restore functions introduced in the
previous patch
- Move listen_sockets list under the restart items in ckpt_ctx
- Rename RESTART_SOCK_LISTENONLY to RESTART_CONN_RESET
Changes in v3:
- Prevent restart from allowing a bind on a <1024 port unless the
user is granted that capability
- Add some sanity checking in the inet_precheck() function to make sure
the values read from the checkpoint image are within acceptable ranges
- Check the result of sock_restore_header_info() and fail if needed
Changes in v2:
- Restore saddr, rcv_saddr, daddr, sport, and dport from the sockaddr
structure instead of saving them separately
- Fix 'sock' naming in sock_cptrst()
- Don't take the queue lock before skb_queue_tail() since it is
done for us
- Allow "listen only" restore behavior if RESTART_SOCK_LISTENONLY
flag is specified on sys_restart()
- Pull the implementation of the list of listening sockets back into
this patch
- Fix dangling printk
- Add some comments around the parent/child restore logic
Cc: netdev@vger.kernel.org
Signed-off-by: Dan Smith <danms@us.ibm.com>
Acked-by: Oren Laadan <orenl@cs.columbia.edu>
Acked-by: Serge Hallyn <serue@us.ibm.com>
Tested-by: Serge E. Hallyn <serue@us.ibm.com>
---
Documentation/checkpoint/usage.txt | 1 +
include/linux/checkpoint.h | 7 +-
include/linux/checkpoint_hdr.h | 97 +++++++++-
include/linux/checkpoint_types.h | 1 +
kernel/checkpoint/sys.c | 6 +
net/checkpoint.c | 19 +-
net/ipv4/checkpoint.c | 373 +++++++++++++++++++++++++++++++++++-
7 files changed, 484 insertions(+), 20 deletions(-)
diff --git a/Documentation/checkpoint/usage.txt b/Documentation/checkpoint/usage.txt
index c6fc045..239c162 100644
--- a/Documentation/checkpoint/usage.txt
+++ b/Documentation/checkpoint/usage.txt
@@ -35,6 +35,7 @@ The API consists of three new system calls:
- RESTART_TASKSELF : (self) restart of a single process
- RESTART_FROEZN : processes remain frozen once restart completes
- RESTART_GHOST : process is a ghost (placeholder for a pid)
+ - RESTART_CONN_RESET : reset previously open sockets to be closed
(Note that this argument may mean 'ckptid' to identify an in-kernel
checkpoint image, with some @flags in the future).
diff --git a/include/linux/checkpoint.h b/include/linux/checkpoint.h
index 25275af..abf8cb5 100644
--- a/include/linux/checkpoint.h
+++ b/include/linux/checkpoint.h
@@ -19,6 +19,7 @@
#define RESTART_TASKSELF 0x1
#define RESTART_FROZEN 0x2
#define RESTART_GHOST 0x4
+#define RESTART_CONN_RESET 0x10
/* misc user visible */
#define CHECKPOINT_FD_NONE -1
@@ -57,7 +58,8 @@ extern long do_sys_restart(pid_t pid, int fd,
#define RESTART_USER_FLAGS \
(RESTART_TASKSELF | \
RESTART_FROZEN | \
- RESTART_GHOST)
+ RESTART_GHOST | \
+ RESTART_CONN_RESET)
extern int walk_task_subtree(struct task_struct *task,
int (*func)(struct task_struct *, void *),
@@ -103,7 +105,8 @@ extern int ckpt_sock_getnames(struct ckpt_ctx *ctx,
struct socket *socket,
struct sockaddr *loc, unsigned *loc_len,
struct sockaddr *rem, unsigned *rem_len);
-extern struct sk_buff *sock_restore_skb(struct ckpt_ctx *ctx);
+extern struct sk_buff *sock_restore_skb(struct ckpt_ctx *ctx, struct sock *sk);
+extern void sock_listening_list_free(struct list_head *head);
/* ckpt kflags */
#define ckpt_set_ctx_kflag(__ctx, __kflag) \
diff --git a/include/linux/checkpoint_hdr.h b/include/linux/checkpoint_hdr.h
index 07934ee..1820f07 100644
--- a/include/linux/checkpoint_hdr.h
+++ b/include/linux/checkpoint_hdr.h
@@ -15,6 +15,7 @@
#include <linux/socket.h>
#include <linux/un.h>
#include <linux/in.h>
+#include <linux/in6.h>
#ifndef CONFIG_CHECKPOINT
#error linux/checkpoint_hdr.h included directly (without CONFIG_CHECKPOINT)
@@ -22,8 +23,6 @@
#else /* __KERNEL__ */
-#else
-
#include <sys/types.h>
#include <linux/types.h>
#include <sys/socket.h>
@@ -586,6 +585,100 @@ struct ckpt_hdr_socket_unix {
struct ckpt_hdr_socket_inet {
struct ckpt_hdr h;
+ __u32 daddr;
+ __u32 rcv_saddr;
+ __u32 saddr;
+ __u16 dport;
+ __u16 num;
+ __u16 sport;
+ __s16 uc_ttl;
+ __u16 cmsg_flags;
+
+ struct {
+ __u64 timeout;
+ __u32 ato;
+ __u32 lrcvtime;
+ __u16 last_seg_size;
+ __u16 rcv_mss;
+ __u8 pending;
+ __u8 quick;
+ __u8 pingpong;
+ __u8 blocked;
+ } icsk_ack __attribute__ ((aligned(8)));
+
+ /* FIXME: Skipped opt, tos, multicast, cork settings */
+
+ struct {
+ __u32 rcv_nxt;
+ __u32 copied_seq;
+ __u32 rcv_wup;
+ __u32 snd_nxt;
+ __u32 snd_una;
+ __u32 snd_sml;
+ __u32 rcv_tstamp;
+ __u32 lsndtime;
+
+ __u32 snd_wl1;
+ __u32 snd_wnd;
+ __u32 max_window;
+ __u32 mss_cache;
+ __u32 window_clamp;
+ __u32 rcv_ssthresh;
+ __u32 frto_highmark;
+
+ __u32 srtt;
+ __u32 mdev;
+ __u32 mdev_max;
+ __u32 rttvar;
+ __u32 rtt_seq;
+
+ __u32 packets_out;
+ __u32 retrans_out;
+
+ __u32 snd_up;
+ __u32 rcv_wnd;
+ __u32 write_seq;
+ __u32 pushed_seq;
+ __u32 lost_out;
+ __u32 sacked_out;
+ __u32 fackets_out;
+ __u32 tso_deferred;
+ __u32 bytes_acked;
+
+ __s32 lost_cnt_hint;
+ __u32 retransmit_high;
+
+ __u32 lost_retrans_low;
+
+ __u32 prior_ssthresh;
+ __u32 high_seq;
+
+ __u32 retrans_stamp;
+ __u32 undo_marker;
+ __s32 undo_retrans;
+ __u32 total_retrans;
+
+ __u32 urg_seq;
+ __u32 keepalive_time;
+ __u32 keepalive_intvl;
+
+ __u16 urg_data;
+ __u16 advmss;
+ __u8 frto_counter;
+ __u8 nonagle;
+
+ __u8 ecn_flags;
+ __u8 reordering;
+
+ __u8 keepalive_probes;
+ } tcp __attribute__ ((aligned(8)));
+
+ struct {
+ struct in6_addr saddr;
+ struct in6_addr rcv_saddr;
+ struct in6_addr daddr;
+ } inet6 __attribute__ ((aligned(8)));
+
__u32 laddr_len;
__u32 raddr_len;
struct sockaddr_in laddr;
diff --git a/include/linux/checkpoint_types.h b/include/linux/checkpoint_types.h
index d69b795..79c6394 100644
--- a/include/linux/checkpoint_types.h
+++ b/include/linux/checkpoint_types.h
@@ -79,6 +79,7 @@ struct ckpt_ctx {
wait_queue_head_t waitq; /* waitqueue for restarting tasks */
wait_queue_head_t ghostq; /* waitqueue for ghost tasks */
struct cred *realcred, *ecred; /* tmp storage for cred at restart */
+ struct list_head listen_sockets;/* listening parent sockets */
struct ckpt_stats stats; /* statistics */
diff --git a/kernel/checkpoint/sys.c b/kernel/checkpoint/sys.c
index 86dabfa..b8369e4 100644
--- a/kernel/checkpoint/sys.c
+++ b/kernel/checkpoint/sys.c
@@ -229,6 +229,10 @@ static void ckpt_ctx_free(struct ckpt_ctx *ctx)
kfree(ctx->pids_arr);
+#ifdef CONFIG_NETNS_CHECKPOINT
+ sock_listening_list_free(&ctx->listen_sockets);
+#endif
+
kfree(ctx);
}
@@ -262,6 +266,8 @@ static struct ckpt_ctx *ckpt_ctx_alloc(int fd, unsigned long uflags,
mutex_init(&ctx->msg_mutex);
+ INIT_LIST_HEAD(&ctx->listen_sockets);
+
err = -EBADF;
ctx->file = fget(fd);
if (!ctx->file)
diff --git a/net/checkpoint.c b/net/checkpoint.c
index d972044..03c1224 100644
--- a/net/checkpoint.c
+++ b/net/checkpoint.c
@@ -115,9 +115,9 @@ static void sock_record_header_info(struct sk_buff *skb,
h->nr_frags = skb_shinfo(skb)->nr_frags;
}
-int sock_restore_header_info(struct ckpt_ctx *ctx,
- struct sk_buff *skb,
- struct ckpt_hdr_socket_buffer *h)
+static int sock_restore_header_info(struct ckpt_ctx *ctx,
+ struct sock *sk, struct sk_buff *skb,
+ struct ckpt_hdr_socket_buffer *h)
{
if (h->mac_header + h->mac_len != h->network_header) {
ckpt_err(ctx, -EINVAL,
@@ -171,6 +171,8 @@ int sock_restore_header_info(struct ckpt_ctx *ctx,
skb->data = skb->head + h->data_offset;
skb->len = h->skb_len;
+ skb_shinfo(skb)->gso_type = sk->sk_gso_type;
+
return 0;
}
@@ -216,7 +218,7 @@ static int sock_restore_skb_frag(struct ckpt_ctx *ctx,
return ret;
}
-struct sk_buff *sock_restore_skb(struct ckpt_ctx *ctx)
+struct sk_buff *sock_restore_skb(struct ckpt_ctx *ctx, struct sock *sk)
{
struct ckpt_hdr_socket_buffer *h;
struct sk_buff *skb = NULL;
@@ -269,7 +271,7 @@ struct sk_buff *sock_restore_skb(struct ckpt_ctx *ctx)
goto out;
}
- sock_restore_header_info(ctx, skb, h);
+ sock_restore_header_info(ctx, sk, skb, h);
out:
ckpt_hdr_put(ctx, h);
if (ret < 0) {
@@ -931,10 +933,9 @@ static void *restore_sock(struct ckpt_ctx *ctx)
goto err;
if ((h->sock_common.family == AF_INET) &&
- (h->sock.state != TCP_LISTEN)) {
- /* Temporary hack to enable restore of TCP_LISTEN sockets
- * while forcing anything else to a closed state
- */
+ (h->sock.state != TCP_LISTEN) &&
+ (ctx->uflags & RESTART_CONN_RESET)) {
+ ckpt_debug("Forcing open socket closed\n");
sock->sk->sk_state = TCP_CLOSE;
sock->state = SS_UNCONNECTED;
}
diff --git a/net/ipv4/checkpoint.c b/net/ipv4/checkpoint.c
index 0b62a15..bc4c998 100644
--- a/net/ipv4/checkpoint.c
+++ b/net/ipv4/checkpoint.c
@@ -16,6 +16,7 @@
#include <linux/checkpoint.h>
#include <net/tcp_states.h>
#include <net/tcp.h>
+#include <net/ipv6.h>
struct dq_sock {
struct ckpt_ctx *ctx;
@@ -27,6 +28,248 @@ struct dq_buffers {
struct sock *sk;
};
+struct listen_item {
+ struct sock *sk;
+ struct list_head list;
+};
+
+void sock_listening_list_free(struct list_head *head)
+{
+ struct listen_item *item, *tmp;
+
+ list_for_each_entry_safe(item, tmp, head, list) {
+ list_del(&item->list);
+ kfree(item);
+ }
+}
+
+static int sock_listening_list_add(struct ckpt_ctx *ctx, struct sock *sk)
+{
+ struct listen_item *item;
+
+ item = kmalloc(sizeof(*item), GFP_KERNEL);
+ if (!item)
+ return -ENOMEM;
+
+ item->sk = sk;
+ list_add(&item->list, &ctx->listen_sockets);
+
+ return 0;
+}
+
+static struct sock *sock_get_parent(struct ckpt_ctx *ctx, struct sock *sk)
+{
+ struct listen_item *item;
+
+ list_for_each_entry(item, &ctx->listen_sockets, list) {
+ if (inet_sk(sk)->inet_sport == inet_sk(item->sk)->inet_sport)
+ return item->sk;
+ }
+
+ return NULL;
+}
+
+static int sock_hash_parent(void *data)
+{
+ struct dq_sock *dq = (struct dq_sock *)data;
+ struct sock *parent;
+
+ ckpt_debug("INET post-restart hash\n");
+
+ dq->sk->sk_prot->hash(dq->sk);
+
+ /* If there is a listening socket with the same source port,
+ * then become a child of that socket [we are the result of an
+ * accept()]. Otherwise hash ourselves directly in [we are
+ * the result of a connect()]
+ */
+
+ parent = sock_get_parent(dq->ctx, dq->sk);
+ if (parent) {
+ inet_sk(dq->sk)->inet_num = ntohs(inet_sk(dq->sk)->inet_sport);
+ local_bh_disable();
+ __inet_inherit_port(parent, dq->sk);
+ local_bh_enable();
+ } else {
+ inet_sk(dq->sk)->inet_num = 0;
+ inet_hash_connect(&tcp_death_row, dq->sk);
+ inet_sk(dq->sk)->inet_num = ntohs(inet_sk(dq->sk)->inet_sport);
+ }
+
+ return 0;
+}
+
+static int sock_defer_hash(struct ckpt_ctx *ctx, struct sock *sock)
+{
+ struct dq_sock dq;
+
+ dq.sk = sock;
+ dq.ctx = ctx;
+
+ return deferqueue_add(ctx->deferqueue, &dq, sizeof(dq),
+ sock_hash_parent, NULL);
+}
+
+static int sock_inet_tcp_cptrst(struct ckpt_ctx *ctx,
+ struct tcp_sock *sk,
+ struct ckpt_hdr_socket_inet *hh,
+ int op)
+{
+ CKPT_COPY(op, hh->tcp.rcv_nxt, sk->rcv_nxt);
+ CKPT_COPY(op, hh->tcp.copied_seq, sk->copied_seq);
+ CKPT_COPY(op, hh->tcp.rcv_wup, sk->rcv_wup);
+ CKPT_COPY(op, hh->tcp.snd_nxt, sk->snd_nxt);
+ CKPT_COPY(op, hh->tcp.snd_una, sk->snd_una);
+ CKPT_COPY(op, hh->tcp.snd_sml, sk->snd_sml);
+ CKPT_COPY(op, hh->tcp.rcv_tstamp, sk->rcv_tstamp);
+ CKPT_COPY(op, hh->tcp.lsndtime, sk->lsndtime);
+
+ CKPT_COPY(op, hh->tcp.snd_wl1, sk->snd_wl1);
+ CKPT_COPY(op, hh->tcp.snd_wnd, sk->snd_wnd);
+ CKPT_COPY(op, hh->tcp.max_window, sk->max_window);
+ CKPT_COPY(op, hh->tcp.mss_cache, sk->mss_cache);
+ CKPT_COPY(op, hh->tcp.window_clamp, sk->window_clamp);
+ CKPT_COPY(op, hh->tcp.rcv_ssthresh, sk->rcv_ssthresh);
+ CKPT_COPY(op, hh->tcp.frto_highmark, sk->frto_highmark);
+ CKPT_COPY(op, hh->tcp.advmss, sk->advmss);
+ CKPT_COPY(op, hh->tcp.frto_counter, sk->frto_counter);
+ CKPT_COPY(op, hh->tcp.nonagle, sk->nonagle);
+
+ CKPT_COPY(op, hh->tcp.srtt, sk->srtt);
+ CKPT_COPY(op, hh->tcp.mdev, sk->mdev);
+ CKPT_COPY(op, hh->tcp.mdev_max, sk->mdev_max);
+ CKPT_COPY(op, hh->tcp.rttvar, sk->rttvar);
+ CKPT_COPY(op, hh->tcp.rtt_seq, sk->rtt_seq);
+
+ CKPT_COPY(op, hh->tcp.packets_out, sk->packets_out);
+ CKPT_COPY(op, hh->tcp.retrans_out, sk->retrans_out);
+
+ CKPT_COPY(op, hh->tcp.urg_data, sk->urg_data);
+ CKPT_COPY(op, hh->tcp.ecn_flags, sk->ecn_flags);
+ CKPT_COPY(op, hh->tcp.reordering, sk->reordering);
+ CKPT_COPY(op, hh->tcp.snd_up, sk->snd_up);
+
+ CKPT_COPY(op, hh->tcp.keepalive_probes, sk->keepalive_probes);
+
+ CKPT_COPY(op, hh->tcp.rcv_wnd, sk->rcv_wnd);
+ CKPT_COPY(op, hh->tcp.write_seq, sk->write_seq);
+ CKPT_COPY(op, hh->tcp.pushed_seq, sk->pushed_seq);
+ CKPT_COPY(op, hh->tcp.lost_out, sk->lost_out);
+ CKPT_COPY(op, hh->tcp.sacked_out, sk->sacked_out);
+ CKPT_COPY(op, hh->tcp.fackets_out, sk->fackets_out);
+ CKPT_COPY(op, hh->tcp.tso_deferred, sk->tso_deferred);
+ CKPT_COPY(op, hh->tcp.bytes_acked, sk->bytes_acked);
+
+ CKPT_COPY(op, hh->tcp.lost_cnt_hint, sk->lost_cnt_hint);
+ CKPT_COPY(op, hh->tcp.retransmit_high, sk->retransmit_high);
+
+ CKPT_COPY(op, hh->tcp.lost_retrans_low, sk->lost_retrans_low);
+
+ CKPT_COPY(op, hh->tcp.prior_ssthresh, sk->prior_ssthresh);
+ CKPT_COPY(op, hh->tcp.high_seq, sk->high_seq);
+
+ CKPT_COPY(op, hh->tcp.retrans_stamp, sk->retrans_stamp);
+ CKPT_COPY(op, hh->tcp.undo_marker, sk->undo_marker);
+ CKPT_COPY(op, hh->tcp.undo_retrans, sk->undo_retrans);
+ CKPT_COPY(op, hh->tcp.total_retrans, sk->total_retrans);
+
+ CKPT_COPY(op, hh->tcp.urg_seq, sk->urg_seq);
+ CKPT_COPY(op, hh->tcp.keepalive_time, sk->keepalive_time);
+ CKPT_COPY(op, hh->tcp.keepalive_intvl, sk->keepalive_intvl);
+
+ if (!skb_queue_empty(&sk->ucopy.prequeue))
+ printk(KERN_ERR "PREQUEUE!\n");
+
+ return 0;
+}
+
+static int sock_inet_restore_connection(struct sock *sk,
+ struct ckpt_hdr_socket_inet *hh)
+{
+ struct inet_sock *inet = inet_sk(sk);
+ int tcp_gso = sk->sk_family == AF_INET ? SKB_GSO_TCPV4 : SKB_GSO_TCPV6;
+
+ inet->inet_daddr = hh->raddr.sin_addr.s_addr;
+ inet->inet_saddr = hh->laddr.sin_addr.s_addr;
+ inet->inet_rcv_saddr = inet->inet_saddr;
+
+ inet->inet_dport = hh->raddr.sin_port;
+ inet->inet_sport = hh->laddr.sin_port;
+
+ if (sk->sk_protocol == IPPROTO_TCP)
+ sk->sk_gso_type = tcp_gso;
+ else if (sk->sk_protocol == IPPROTO_UDP)
+ sk->sk_gso_type = SKB_GSO_UDP;
+ else {
+ ckpt_debug("Unsupported socket type while setting GSO\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sock_inet_cptrst(struct ckpt_ctx *ctx,
+ struct sock *sk,
+ struct ckpt_hdr_socket_inet *hh,
+ int op)
+{
+ struct inet_sock *inet = inet_sk(sk);
+ struct inet_connection_sock *icsk = inet_csk(sk);
+ int ret;
+
+ if (op == CKPT_CPT) {
+ CKPT_COPY(op, hh->daddr, inet->inet_daddr);
+ CKPT_COPY(op, hh->rcv_saddr, inet->inet_rcv_saddr);
+ CKPT_COPY(op, hh->dport, inet->inet_dport);
+ CKPT_COPY(op, hh->saddr, inet->inet_saddr);
+ CKPT_COPY(op, hh->sport, inet->inet_sport);
+ } else {
+ ret = sock_inet_restore_connection(sk, hh);
+ if (ret)
+ return ret;
+ }
+
+ CKPT_COPY(op, hh->num, inet->inet_num);
+ CKPT_COPY(op, hh->uc_ttl, inet->uc_ttl);
+ CKPT_COPY(op, hh->cmsg_flags, inet->cmsg_flags);
+
+ CKPT_COPY(op, hh->icsk_ack.pending, icsk->icsk_ack.pending);
+ CKPT_COPY(op, hh->icsk_ack.quick, icsk->icsk_ack.quick);
+ CKPT_COPY(op, hh->icsk_ack.pingpong, icsk->icsk_ack.pingpong);
+ CKPT_COPY(op, hh->icsk_ack.blocked, icsk->icsk_ack.blocked);
+ CKPT_COPY(op, hh->icsk_ack.ato, icsk->icsk_ack.ato);
+ CKPT_COPY(op, hh->icsk_ack.timeout, icsk->icsk_ack.timeout);
+ CKPT_COPY(op, hh->icsk_ack.lrcvtime, icsk->icsk_ack.lrcvtime);
+ CKPT_COPY(op,
+ hh->icsk_ack.last_seg_size, icsk->icsk_ack.last_seg_size);
+ CKPT_COPY(op, hh->icsk_ack.rcv_mss, icsk->icsk_ack.rcv_mss);
+
+ if (sk->sk_protocol == IPPROTO_TCP)
+ ret = sock_inet_tcp_cptrst(ctx, tcp_sk(sk), hh, op);
+ else if (sk->sk_protocol == IPPROTO_UDP)
+ ret = 0;
+ else {
+ ret = -EINVAL;
+ ckpt_err(ctx, ret, "unknown socket protocol %d",
+ sk->sk_protocol);
+ }
+
+ if (sk->sk_family == AF_INET6) {
+ struct ipv6_pinfo *inet6 = inet6_sk(sk);
+ if (op == CKPT_CPT) {
+ ipv6_addr_copy(&hh->inet6.saddr, &inet6->saddr);
+ ipv6_addr_copy(&hh->inet6.rcv_saddr, &inet6->rcv_saddr);
+ ipv6_addr_copy(&hh->inet6.daddr, &inet6->daddr);
+ } else {
+ ipv6_addr_copy(&inet6->saddr, &hh->inet6.saddr);
+ ipv6_addr_copy(&inet6->rcv_saddr, &hh->inet6.rcv_saddr);
+ ipv6_addr_copy(&inet6->daddr, &hh->inet6.daddr);
+ }
+ }
+
+ return ret;
+}
+
int inet_checkpoint(struct ckpt_ctx *ctx, struct socket *sock)
{
struct ckpt_hdr_socket_inet *in;
@@ -42,6 +285,10 @@ int inet_checkpoint(struct ckpt_ctx *ctx, struct socket *sock)
if (ret)
goto out;
+ ret = sock_inet_cptrst(ctx, sock->sk, in, CKPT_CPT);
+ if (ret < 0)
+ goto out;
+
ret = ckpt_write_obj(ctx, (struct ckpt_hdr *) in);
out:
ckpt_hdr_put(ctx, in);
@@ -54,11 +301,13 @@ int inet_collect(struct ckpt_ctx *ctx, struct socket *sock)
return ckpt_obj_collect(ctx, sock->sk, CKPT_OBJ_SOCK);
}
-static int inet_read_buffer(struct ckpt_ctx *ctx, struct sk_buff_head *queue)
+static int inet_read_buffer(struct ckpt_ctx *ctx,
+ struct sk_buff_head *queue,
+ struct sock *sk)
{
struct sk_buff *skb = NULL;
- skb = sock_restore_skb(ctx);
+ skb = sock_restore_skb(ctx, sk);
if (IS_ERR(skb))
return PTR_ERR(skb);
@@ -66,7 +315,9 @@ static int inet_read_buffer(struct ckpt_ctx *ctx, struct sk_buff_head *queue)
return skb->len;
}
-static int inet_read_buffers(struct ckpt_ctx *ctx, struct sk_buff_head *queue)
+static int inet_read_buffers(struct ckpt_ctx *ctx,
+ struct sk_buff_head *queue,
+ struct sock *sk)
{
struct ckpt_hdr_socket_queue *h;
int ret = 0;
@@ -77,7 +328,7 @@ static int inet_read_buffers(struct ckpt_ctx *ctx, struct sk_buff_head *queue)
return PTR_ERR(h);
for (i = 0; i < h->skb_count; i++) {
- ret = inet_read_buffer(ctx, queue);
+ ret = inet_read_buffer(ctx, queue, sk);
ckpt_debug("read inet buffer %i: %i", i, ret);
if (ret < 0)
goto out;
@@ -105,12 +356,12 @@ static int inet_deferred_restore_buffers(void *data)
struct sock *sk = dq->sk;
int ret;
- ret = inet_read_buffers(ctx, &sk->sk_receive_queue);
+ ret = inet_read_buffers(ctx, &sk->sk_receive_queue, sk);
ckpt_debug("(R) inet_read_buffers: %i\n", ret);
if (ret < 0)
return ret;
- ret = inet_read_buffers(ctx, &sk->sk_write_queue);
+ ret = inet_read_buffers(ctx, &sk->sk_write_queue, sk);
ckpt_debug("(W) inet_read_buffers: %i\n", ret);
return ret;
@@ -129,6 +380,19 @@ static int inet_defer_restore_buffers(struct ckpt_ctx *ctx, struct sock *sk)
static int inet_precheck(struct socket *sock, struct ckpt_hdr_socket_inet *in)
{
+ __u8 icsk_ack_mask = ICSK_ACK_SCHED | ICSK_ACK_TIMER |
+ ICSK_ACK_PUSHED | ICSK_ACK_PUSHED2;
+ __u16 urg_mask = TCP_URG_VALID | TCP_URG_NOTYET | TCP_URG_READ;
+ __u8 nonagle_mask = TCP_NAGLE_OFF | TCP_NAGLE_CORK | TCP_NAGLE_PUSH;
+ __u8 ecn_mask = TCP_ECN_OK | TCP_ECN_QUEUE_CWR | TCP_ECN_DEMAND_CWR;
+
+ if ((htons(in->laddr.sin_port) < PROT_SOCK) &&
+ !capable(CAP_NET_BIND_SERVICE)) {
+ ckpt_debug("unable to bind to port %hu\n",
+ htons(in->laddr.sin_port));
+ return -EINVAL;
+ }
+
if (in->laddr_len > sizeof(struct sockaddr_in)) {
ckpt_debug("laddr_len is too big\n");
return -EINVAL;
@@ -139,6 +403,75 @@ static int inet_precheck(struct socket *sock, struct ckpt_hdr_socket_inet *in)
return -EINVAL;
}
+ /* Set ato to the default */
+ in->icsk_ack.ato = TCP_ATO_MIN;
+
+ /* No quick acks are scheduled after a restart */
+ in->icsk_ack.quick = 0;
+
+ if (in->icsk_ack.pending & ~icsk_ack_mask) {
+ ckpt_debug("invalid pending flags 0x%x\n",
+ in->icsk_ack.pending & ~icsk_ack_mask);
+ return -EINVAL;
+ }
+
+ if (in->icsk_ack.pingpong > 1) {
+ ckpt_debug("invalid icsk_ack.pingpong value\n");
+ return -EINVAL;
+ }
+
+ if (in->icsk_ack.blocked > 1) {
+ ckpt_debug("invalid icsk_ack.blocked value\n");
+ return -EINVAL;
+ }
+
+ /* do_tcp_setsockopt() quietly makes this coercion */
+ if (in->tcp.window_clamp < (SOCK_MIN_RCVBUF / 2))
+ in->tcp.window_clamp = SOCK_MIN_RCVBUF / 2;
+ else
+ in->tcp.window_clamp = min(in->tcp.window_clamp, 65535U);
+
+ if (in->tcp.rcv_ssthresh > (4U * in->tcp.advmss))
+ in->tcp.rcv_ssthresh = 4U * in->tcp.advmss;
+
+ /* These will all be recalculated on the next call to
+ * tcp_rtt_estimator()
+ */
+ in->tcp.srtt = in->tcp.mdev = in->tcp.mdev_max = 0;
+ in->tcp.rttvar = in->tcp.rtt_seq = 0;
+
+ /* Might want to set packets_out to zero ? */
+
+ if (in->tcp.rcv_wnd > MAX_TCP_WINDOW)
+ in->tcp.rcv_wnd = MAX_TCP_WINDOW;
+
+ if (in->tcp.keepalive_intvl > MAX_TCP_KEEPINTVL) {
+ ckpt_debug("keepalive_intvl %i out of range\n",
+ in->tcp.keepalive_intvl);
+ return -EINVAL;
+ }
+
+ if (in->tcp.keepalive_probes > MAX_TCP_KEEPCNT) {
+ ckpt_debug("Invalid keepalive_probes value %i\n",
+ in->tcp.keepalive_probes);
+ return -EINVAL;
+ }
+
+ if (in->tcp.urg_data & ~urg_mask) {
+ ckpt_debug("Invalid urg_data value\n");
+ return -EINVAL;
+ }
+
+ if (in->tcp.nonagle & ~nonagle_mask) {
+ ckpt_debug("Invalid nonagle value\n");
+ return -EINVAL;
+ }
+
+ if (in->tcp.ecn_flags & ~ecn_mask) {
+ ckpt_debug("Invalid ecn_flags value\n");
+ return -EINVAL;
+ }
+
return 0;
}
@@ -176,8 +509,35 @@ int inet_restore(struct ckpt_ctx *ctx,
ckpt_debug("inet listen: %i\n", ret);
if (ret < 0)
goto out;
+
+ /* We are a listening socket, so add ourselves
+ * to the list of parent sockets. This will
+ * allow our children to find us later and
+ * link up
+ */
+
+ ret = sock_listening_list_add(ctx, sock->sk);
+ if (ret < 0)
+ goto out;
}
} else {
+ ret = sock_inet_cptrst(ctx, sock->sk, in, CKPT_RST);
+ if (ret)
+ goto out;
+
+ if ((h->sock.state == TCP_ESTABLISHED) &&
+ (h->sock.protocol == IPPROTO_TCP)) {
+ /* A connected socket that was spawned from an
+ * accept() needs to be hashed with its parent
+ * listening socket in order to receive
+ * traffic on the original port. Since we may
+ * not have restarted the parent yet, we defer
+ * this until later when we know we have all
+ * the listening sockets accounted for.
+ */
+ ret = sock_defer_hash(ctx, sock->sk);
+ }
+
if (!sock_flag(sock->sk, SOCK_DEAD))
ret = inet_defer_restore_buffers(ctx, sock->sk);
}
@@ -186,4 +546,3 @@ int inet_restore(struct ckpt_ctx *ctx,
return ret;
}
-
--
1.6.3.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v21 093/100] c/r: Add checkpoint and collect hooks to net_device_ops
[not found] <1272723382-19470-1-git-send-email-orenl@cs.columbia.edu>
` (6 preceding siblings ...)
2010-05-01 14:15 ` [PATCH v21 075/100] c/r: add support for connected INET sockets (v5) Oren Laadan
@ 2010-05-01 14:16 ` Oren Laadan
2010-05-01 14:16 ` [PATCH v21 094/100] c/r: Basic support for network namespaces and devices (v6) Oren Laadan
` (5 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Oren Laadan @ 2010-05-01 14:16 UTC (permalink / raw)
To: Andrew Morton
Cc: containers, linux-kernel, Serge Hallyn, Matt Helsley,
Pavel Emelyanov, Dan Smith, netdev
From: Dan Smith <danms@us.ibm.com>
These will be implemented per-driver by those that support such
operations.
Cc: netdev@vger.kernel.org
Signed-off-by: Dan Smith <danms@us.ibm.com>
Acked-by: David S. Miller <davem@davemloft.net>
Acked-by: Serge Hallyn <serue@us.ibm.com>
Acked-by: Oren Laadan <orenl@cs.columbia.edu>
---
include/linux/netdevice.h | 6 ++++++
1 files changed, 6 insertions(+), 0 deletions(-)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index fa8b476..9f6de34 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -691,6 +691,12 @@ struct net_device_ops {
int (*ndo_fcoe_get_wwn)(struct net_device *dev,
u64 *wwn, int type);
#endif
+#ifdef CONFIG_CHECKPOINT
+ int (*ndo_collect)(struct ckpt_ctx *ctx,
+ struct net_device *dev);
+ int (*ndo_checkpoint)(struct ckpt_ctx *ctx,
+ struct net_device *dev);
+#endif
};
/*
--
1.6.3.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v21 094/100] c/r: Basic support for network namespaces and devices (v6)
[not found] <1272723382-19470-1-git-send-email-orenl@cs.columbia.edu>
` (7 preceding siblings ...)
2010-05-01 14:16 ` [PATCH v21 093/100] c/r: Add checkpoint and collect hooks to net_device_ops Oren Laadan
@ 2010-05-01 14:16 ` Oren Laadan
2010-05-01 14:16 ` [PATCH v21 095/100] c/r: Add rtnl_dellink() helper Oren Laadan
` (4 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Oren Laadan @ 2010-05-01 14:16 UTC (permalink / raw)
To: Andrew Morton
Cc: containers, linux-kernel, Serge Hallyn, Matt Helsley,
Pavel Emelyanov, Dan Smith, netdev
From: Dan Smith <danms@us.ibm.com>
When checkpointing a task tree with network namespaces, we hook into
do_checkpoint_ns() along with the others. Any devices in a given namespace
are checkpointed (including their peer, in the case of veth) sequentially.
Each network device stores a list of protocol addresses, as well as other
information, such as hardware address.
This patch supports veth pairs, as well as the loopback adapter. The
loopback support is there to make sure that any additional addresses and
state (such as up/down) is copied to the loopback adapter that we are
given in the new network namespace.
On restart, we instantiate new network namespaces and veth pairs as
necessary. Any device we encounter that isn't in a network namespace
that was checkpointed as part of a task is left in the namespace of the
restarting process. This will be the case for a veth half that exists
in the init netns to provide network access to a container.
Still to do are:
1. Routes
2. Netfilter rules
3. IPv6 addresses
4. Other virtual device types (e.g. bridges)
5. Multicast
6. Device config info (ipv4_devconf)
7. Additional ipv4 address attributes
Changelog[v21]:
- Do not include checkpoint_hdr.h explicitly
- Fix acquiring socket lock before reading RTNETLINK response
- Skip down interfaces (v2)
- Export net checkpoint fns
- Add CHECKPOINT_NETNS flag
- Rename CONFIG_CHECKPOINT_NETNS -> CONFIG_NETNS_CHECKPOINT
- Netdev restore function dispatching from a table
- Added a comment about the controverial determination of "initial netns"
- Simplify the E2BIG error handling
- Remove a redundant check for checkpoint support per-device
Changes in v6:
- Store addresses in network byte order, per Dave's recommendation
Changes in v5:
- Rebase
- Remove checkpoint_container() noise
- Factor out some common bits of the RTNL newlink operations
- Add macvlan support
Changes in v4:
- Fix allocation under lock in ckpt_netdev_inet_addrs()
- Add comment for case where there is no netns info in checkpoint image
- Fix inner structure alignment in netdev_addr header
- Fix instances of kfree(skb)
- Remove init_netns_ref from container header and checkpoint context
- Add 'extern' to checkpoint.h prototypes
- Swizzle do_restore_netns() to handle netns more like the others
- Return E2BIG for failure case when collecting inet addrs
- Report case where device doesn't support checkpoint
- Remove nested netns check from may_checkpoint_task()
- Move veth-specific netdev attributes into unioned struct to set an
example for specific attributes of additional device types
- Add 'sit' device restore path that doesn't really do anything
- Fail instead of skip when encountering a device with no checkpoint
support
Changes in v3:
- Use dev->checkpoint() for per-device checkpoint operation
- Use RTNL for veth pair creation on restart
- Export some of the functions that will be needed by dev->ndo_checkpoint()
Changes in v2:
- Add CONFIG_CHECKPOINT_NETNS that is dependent on NET, NET_NS, and
CHECKPOINT. Conditionally compile the checkpoint_dev code based on it.
- Updated comment on should_checkpoint_netdev()
- Updated checkpoint_netdev() to explicitly check for "veth" in name
- Changed checkpoint_netns() to use BUG() for impossible condition
- Fixed a bug on restart with all devices in the init netns
- Lock the dev_base_lock while traversing interface addresses
- Collect all addresses for an interface before writing out in one
single pass
Cc: netdev@vger.kernel.org
Signed-off-by: Dan Smith <danms@us.ibm.com>
Acked-by: David S. Miller <davem@davemloft.net>
Acked-by: Serge Hallyn <serue@us.ibm.com>
Acked-by: Oren Laadan <orenl@cs.columbia.edu>
---
Documentation/checkpoint/usage.txt | 1 +
include/linux/checkpoint.h | 29 ++-
include/linux/checkpoint_hdr.h | 58 +++
kernel/checkpoint/checkpoint.c | 5 -
kernel/nsproxy.c | 24 +-
net/Kconfig | 4 +
net/Makefile | 1 +
net/checkpoint.c | 63 +++-
net/checkpoint_dev.c | 818 ++++++++++++++++++++++++++++++++++++
9 files changed, 995 insertions(+), 8 deletions(-)
create mode 100644 net/checkpoint_dev.c
diff --git a/Documentation/checkpoint/usage.txt b/Documentation/checkpoint/usage.txt
index d697ed1..5700448 100644
--- a/Documentation/checkpoint/usage.txt
+++ b/Documentation/checkpoint/usage.txt
@@ -15,6 +15,7 @@ The API consists of three new system calls:
an open file to which error and debug messages are written. @flags
may be one or more of:
- CHECKPOINT_SUBTREE : allow checkpoint of sub-container
+ - CHECKPOINT_NETNS : include network namespaces and devices
(other value are not allowed).
Returns: a positive checkpoint identifier (ckptid) upon success, 0 if
diff --git a/include/linux/checkpoint.h b/include/linux/checkpoint.h
index 43d67ce..84bb7a9 100644
--- a/include/linux/checkpoint.h
+++ b/include/linux/checkpoint.h
@@ -14,6 +14,7 @@
/* checkpoint user flags */
#define CHECKPOINT_SUBTREE 0x1
+#define CHECKPOINT_NETNS 0x2
/* restart user flags */
#define RESTART_TASKSELF 0x1
@@ -35,6 +36,7 @@
#include <linux/checkpoint_types.h>
#include <linux/checkpoint_hdr.h>
#include <linux/err.h>
+#include <linux/inetdevice.h>
#include <net/sock.h>
/* sycall helpers */
@@ -55,7 +57,10 @@ extern long do_sys_restart(pid_t pid, int fd,
#define CKPT_CTX_ERROR (1 << CKPT_CTX_ERROR_BIT)
/* ckpt_ctx: uflags */
-#define CHECKPOINT_USER_FLAGS CHECKPOINT_SUBTREE
+#define CHECKPOINT_USER_FLAGS \
+ (CHECKPOINT_SUBTREE | \
+ CHECKPOINT_NETNS)
+
#define RESTART_USER_FLAGS \
(RESTART_TASKSELF | \
RESTART_FROZEN | \
@@ -119,6 +124,28 @@ extern int ckpt_sock_getnames(struct ckpt_ctx *ctx,
extern struct sk_buff *sock_restore_skb(struct ckpt_ctx *ctx, struct sock *sk);
extern void sock_listening_list_free(struct list_head *head);
+#ifdef CONFIG_NETNS_CHECKPOINT
+extern int checkpoint_netns(struct ckpt_ctx *ctx, void *ptr);
+extern void *restore_netns(struct ckpt_ctx *ctx);
+extern int checkpoint_netdev(struct ckpt_ctx *ctx, void *ptr);
+extern void *restore_netdev(struct ckpt_ctx *ctx);
+
+extern int ckpt_netdev_in_init_netns(struct ckpt_ctx *ctx,
+ struct net_device *dev);
+extern int ckpt_netdev_inet_addrs(struct in_device *indev,
+ struct ckpt_netdev_addr *list[]);
+extern int ckpt_netdev_hwaddr(struct net_device *dev,
+ struct ckpt_hdr_netdev *h);
+extern struct ckpt_hdr_netdev *ckpt_netdev_base(struct ckpt_ctx *ctx,
+ struct net_device *dev,
+ struct ckpt_netdev_addr *addrs[]);
+#else
+# define checkpoint_netns NULL
+# define restore_netns NULL
+# define checkpoint_netdev NULL
+# define restore_netdev NULL
+#endif
+
/* ckpt kflags */
#define ckpt_set_ctx_kflag(__ctx, __kflag) \
set_bit(__kflag##_BIT, &(__ctx)->kflags)
diff --git a/include/linux/checkpoint_hdr.h b/include/linux/checkpoint_hdr.h
index 1564726..eb5e1b4 100644
--- a/include/linux/checkpoint_hdr.h
+++ b/include/linux/checkpoint_hdr.h
@@ -189,6 +189,12 @@ enum {
#define CKPT_HDR_SOCKET_UNIX CKPT_HDR_SOCKET_UNIX
CKPT_HDR_SOCKET_INET,
#define CKPT_HDR_SOCKET_INET CKPT_HDR_SOCKET_INET
+ CKPT_HDR_NET_NS,
+#define CKPT_HDR_NET_NS CKPT_HDR_NET_NS
+ CKPT_HDR_NETDEV,
+#define CKPT_HDR_NETDEV CKPT_HDR_NETDEV
+ CKPT_HDR_NETDEV_ADDR,
+#define CKPT_HDR_NETDEV_ADDR CKPT_HDR_NETDEV_ADDR
CKPT_HDR_TAIL = 9001,
#define CKPT_HDR_TAIL CKPT_HDR_TAIL
@@ -261,6 +267,10 @@ enum obj_type {
#define CKPT_OBJ_SECURITY_PTR CKPT_OBJ_SECURITY_PTR
CKPT_OBJ_SECURITY,
#define CKPT_OBJ_SECURITY CKPT_OBJ_SECURITY
+ CKPT_OBJ_NET_NS,
+#define CKPT_OBJ_NET_NS CKPT_OBJ_NET_NS
+ CKPT_OBJ_NETDEV,
+#define CKPT_OBJ_NETDEV CKPT_OBJ_NETDEV
CKPT_OBJ_MAX
#define CKPT_OBJ_MAX CKPT_OBJ_MAX
};
@@ -444,6 +454,7 @@ struct ckpt_hdr_ns {
struct ckpt_hdr h;
__s32 uts_objref;
__s32 ipc_objref;
+ __s32 net_objref;
} __attribute__((aligned(8)));
/* cannot include <linux/tty.h> from userspace, so define: */
@@ -768,6 +779,53 @@ struct ckpt_hdr_file_socket {
__s32 sock_objref;
} __attribute__((aligned(8)));
+struct ckpt_hdr_netns {
+ struct ckpt_hdr h;
+ __s32 this_ref;
+} __attribute__((aligned(8)));
+
+enum ckpt_netdev_types {
+ CKPT_NETDEV_LO,
+ CKPT_NETDEV_VETH,
+ CKPT_NETDEV_SIT,
+ CKPT_NETDEV_MACVLAN,
+ CKPT_NETDEV_MAX,
+};
+
+struct ckpt_hdr_netdev {
+ struct ckpt_hdr h;
+ __s32 netns_ref;
+ union {
+ struct {
+ __s32 this_ref;
+ __s32 peer_ref;
+ } veth;
+ struct {
+ __u32 mode;
+ } macvlan;
+ };
+ __u32 inet_addrs;
+ __u16 type;
+ __u16 flags;
+ __u8 hwaddr[6];
+} __attribute__((aligned(8)));
+
+enum ckpt_netdev_addr_types {
+ CKPT_NETDEV_ADDR_IPV4,
+};
+
+struct ckpt_netdev_addr {
+ __u16 type;
+ union {
+ struct {
+ __be32 inet4_local;
+ __be32 inet4_address;
+ __be32 inet4_mask;
+ __be32 inet4_broadcast;
+ };
+ } __attribute__((aligned(8)));
+} __attribute__((aligned(8)));
+
struct ckpt_hdr_eventpoll_items {
struct ckpt_hdr h;
__s32 epfile_objref;
diff --git a/kernel/checkpoint/checkpoint.c b/kernel/checkpoint/checkpoint.c
index 7a4f1ce..4059c28 100644
--- a/kernel/checkpoint/checkpoint.c
+++ b/kernel/checkpoint/checkpoint.c
@@ -291,11 +291,6 @@ static int may_checkpoint_task(struct ckpt_ctx *ctx, struct task_struct *t)
_ckpt_err(ctx, -EPERM, "%(T)Nested mnt_ns unsupported\n");
ret = -EPERM;
}
- /* no support for >1 private netns */
- if (nsproxy->net_ns != ctx->root_nsproxy->net_ns) {
- _ckpt_err(ctx, -EPERM, "%(T)Nested net_ns unsupported\n");
- ret = -EPERM;
- }
/* pidns must be descendent of root_nsproxy */
pidns = nsproxy->pid_ns;
while (pidns != ctx->root_nsproxy->pid_ns) {
diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c
index 7fb3cea..d4af91d 100644
--- a/kernel/nsproxy.c
+++ b/kernel/nsproxy.c
@@ -260,6 +260,12 @@ int ckpt_collect_ns(struct ckpt_ctx *ctx, struct task_struct *t)
ret = ckpt_obj_collect(ctx, nsproxy->uts_ns, CKPT_OBJ_UTS_NS);
if (ret < 0)
goto out;
+#ifdef CONFIG_NETNS_CHECKPOINT
+ if (ctx->uflags & CHECKPOINT_NETNS)
+ ret = ckpt_obj_collect(ctx, nsproxy->net_ns, CKPT_OBJ_NET_NS);
+ if (ret < 0)
+ goto out;
+#endif
#ifdef CONFIG_IPC_NS
ret = ckpt_obj_collect(ctx, nsproxy->ipc_ns, CKPT_OBJ_IPC_NS);
if (ret < 0)
@@ -308,6 +314,15 @@ static int checkpoint_ns(struct ckpt_ctx *ctx, void *ptr)
#endif /* CONFIG_IPC_NS */
h->ipc_objref = ret;
+#ifdef CONFIG_NETNS_CHECKPOINT
+ if (ctx->uflags & CHECKPOINT_NETNS)
+ ret = checkpoint_obj(ctx, nsproxy->net_ns, CKPT_OBJ_NET_NS);
+ else
+ ret = 0;
+ if (ret < 0)
+ goto out;
+ h->net_objref = ret;
+#endif
/* FIXME: for now, only marked visited to pacify leaks */
ret = ckpt_obj_visit(ctx, nsproxy->mnt_ns, CKPT_OBJ_MNT_NS);
if (ret < 0)
@@ -341,6 +356,14 @@ static void *restore_ns(struct ckpt_ctx *ctx)
ret = PTR_ERR(uts_ns);
goto out;
}
+ if (h->net_objref == 0)
+ net_ns = current->nsproxy->net_ns;
+ else
+ net_ns = ckpt_obj_fetch(ctx, h->net_objref, CKPT_OBJ_NET_NS);
+ if (IS_ERR(net_ns)) {
+ ret = PTR_ERR(net_ns);
+ goto out;
+ }
if (h->ipc_objref == 0)
ipc_ns = ctx->root_nsproxy->ipc_ns;
@@ -356,7 +379,6 @@ static void *restore_ns(struct ckpt_ctx *ctx)
}
mnt_ns = ctx->root_nsproxy->mnt_ns;
- net_ns = ctx->root_nsproxy->net_ns;
if (uts_ns == current->nsproxy->uts_ns &&
ipc_ns == current->nsproxy->ipc_ns &&
diff --git a/net/Kconfig b/net/Kconfig
index 041c35e..c1cb774 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -276,4 +276,8 @@ source "net/wimax/Kconfig"
source "net/rfkill/Kconfig"
source "net/9p/Kconfig"
+config NETNS_CHECKPOINT
+ bool
+ default y if NET && NET_NS && CHECKPOINT
+
endif # if NET
diff --git a/net/Makefile b/net/Makefile
index 74b038f..b7d78f4 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -67,3 +67,4 @@ endif
obj-$(CONFIG_WIMAX) += wimax/
obj-$(CONFIG_CHECKPOINT) += checkpoint.o
+obj-$(CONFIG_NETNS_CHECKPOINT) += checkpoint_dev.o
diff --git a/net/checkpoint.c b/net/checkpoint.c
index 03c1224..b1f56bf 100644
--- a/net/checkpoint.c
+++ b/net/checkpoint.c
@@ -986,6 +986,56 @@ struct file *sock_file_restore(struct ckpt_ctx *ctx, struct ckpt_hdr_file *ptr)
* sock-related checkpoint objects
*/
+static int netns_grab(void *ptr)
+{
+ struct net *net = ptr;
+
+ get_net(net);
+ return 0;
+}
+
+static void netns_drop(void *ptr, int lastref)
+{
+ struct net *net = ptr;
+
+ put_net(net);
+}
+
+/* netns object */
+static const struct ckpt_obj_ops ckpt_obj_netns_ops = {
+ .obj_name = "NET_NS",
+ .obj_type = CKPT_OBJ_NET_NS,
+ .ref_grab = netns_grab,
+ .ref_drop = netns_drop,
+ .checkpoint = checkpoint_netns,
+ .restore = restore_netns,
+};
+
+static int netdev_grab(void *ptr)
+{
+ struct net_device *dev = ptr;
+
+ dev_hold(dev);
+ return 0;
+}
+
+static void netdev_drop(void *ptr, int lastref)
+{
+ struct net_device *dev = ptr;
+
+ dev_put(dev);
+}
+
+/* netdev object */
+static const struct ckpt_obj_ops ckpt_obj_netdev_ops = {
+ .obj_name = "NET_DEV",
+ .obj_type = CKPT_OBJ_NETDEV,
+ .ref_grab = netdev_grab,
+ .ref_drop = netdev_drop,
+ .checkpoint = checkpoint_netdev,
+ .restore = restore_netdev,
+};
+
static int obj_sock_grab(void *ptr)
{
sock_hold((struct sock *) ptr);
@@ -1033,6 +1083,17 @@ static const struct ckpt_obj_ops ckpt_obj_sock_ops = {
static int __init checkpoint_register_sock(void)
{
- return register_checkpoint_obj(&ckpt_obj_sock_ops);
+ int ret;
+
+ ret = register_checkpoint_obj(&ckpt_obj_sock_ops);
+ if (ret < 0)
+ return ret;
+ ret = register_checkpoint_obj(&ckpt_obj_netns_ops);
+ if (ret < 0)
+ return ret;
+ ret = register_checkpoint_obj(&ckpt_obj_netdev_ops);
+ if (ret < 0)
+ return ret;
+ return 0;
}
module_init(checkpoint_register_sock);
diff --git a/net/checkpoint_dev.c b/net/checkpoint_dev.c
new file mode 100644
index 0000000..34a6bdb
--- /dev/null
+++ b/net/checkpoint_dev.c
@@ -0,0 +1,818 @@
+/*
+ * Copyright 2010 IBM Corporation
+ *
+ * Author(s): Dan Smith <danms@us.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#include <linux/sched.h>
+#include <linux/if.h>
+#include <linux/if_arp.h>
+#include <linux/inetdevice.h>
+#include <linux/veth.h>
+#include <linux/checkpoint.h>
+#include <linux/deferqueue.h>
+
+#include <net/net_namespace.h>
+#include <net/sch_generic.h>
+
+struct dq_netdev {
+ struct net_device *dev;
+ struct ckpt_ctx *ctx;
+};
+
+struct veth_newlink {
+ char *peer;
+};
+
+struct mvl_newlink {
+ char this[IFNAMSIZ+1];
+ char base[IFNAMSIZ+1];
+ int mode;
+ __u8 *hwaddr;
+};
+
+typedef int (*new_link_fn)(struct sk_buff *, void *);
+
+static int __kern_devinet_ioctl(struct net *net, unsigned int cmd, void *arg)
+{
+ mm_segment_t fs;
+ int ret;
+
+ fs = get_fs();
+ set_fs(KERNEL_DS);
+ ret = devinet_ioctl(net, cmd, arg);
+ set_fs(fs);
+
+ return ret;
+}
+
+static int __kern_dev_ioctl(struct net *net, unsigned int cmd, void *arg)
+{
+ mm_segment_t fs;
+ int ret;
+
+ fs = get_fs();
+ set_fs(KERNEL_DS);
+ ret = dev_ioctl(net, cmd, arg);
+ set_fs(fs);
+
+ return ret;
+}
+
+static struct socket *rtnl_open(void)
+{
+ struct socket *sock;
+ int ret;
+
+ ret = sock_create(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE, &sock);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ return sock;
+}
+
+static int rtnl_close(struct socket *rtnl)
+{
+ if (rtnl)
+ return kernel_sock_shutdown(rtnl, SHUT_RDWR);
+ else
+ return 0;
+}
+
+static struct nlmsghdr *rtnl_get_response(struct socket *rtnl,
+ struct sk_buff **skb)
+{
+ int ret;
+ long timeo = MAX_SCHEDULE_TIMEOUT;
+ struct nlmsghdr *nlh;
+
+ *skb = NULL;
+
+ lock_sock(rtnl->sk);
+ ret = sk_wait_data(rtnl->sk, &timeo);
+ if (ret)
+ *skb = skb_dequeue(&rtnl->sk->sk_receive_queue);
+ release_sock(rtnl->sk);
+
+ if (!*skb)
+ return ERR_PTR(-EPIPE);
+
+ ret = -EINVAL;
+ nlh = nlmsg_hdr(*skb);
+ if (!nlh)
+ goto err;
+
+ if (nlh->nlmsg_type == NLMSG_ERROR) {
+ struct nlmsgerr *errmsg = nlmsg_data(nlh);
+ ret = errmsg->error;
+ goto err;
+ }
+
+ return nlh;
+ err:
+ kfree_skb(*skb);
+ *skb = NULL;
+
+ return ERR_PTR(ret);
+}
+
+int ckpt_netdev_in_init_netns(struct ckpt_ctx *ctx, struct net_device *dev)
+{
+ /*
+ * Currently, we treat the "initial network namespace" as that
+ * of the process doing the checkpoint. This gives us a
+ * consistent view of the container and its layout from the
+ * perspective of the "agent" doing the checkpoint and
+ * restore.
+ */
+ return dev->nd_net == current->nsproxy->net_ns;
+}
+EXPORT_SYMBOL_GPL(ckpt_netdev_in_init_netns);
+
+int ckpt_netdev_hwaddr(struct net_device *dev, struct ckpt_hdr_netdev *h)
+{
+ struct net *net = dev->nd_net;
+ struct ifreq req;
+ int ret;
+
+ memcpy(req.ifr_name, dev->name, IFNAMSIZ);
+ ret = __kern_dev_ioctl(net, SIOCGIFFLAGS, &req);
+ if (ret < 0)
+ return ret;
+ h->flags = req.ifr_flags;
+
+ ret = __kern_dev_ioctl(net, SIOCGIFHWADDR, &req);
+ if (ret < 0)
+ return ret;
+
+ memcpy(h->hwaddr, req.ifr_hwaddr.sa_data, sizeof(h->hwaddr));
+
+ return 0;
+}
+
+int ckpt_netdev_inet_addrs(struct in_device *indev,
+ struct ckpt_netdev_addr *_abuf[])
+{
+ struct ckpt_netdev_addr *abuf = NULL;
+ struct in_ifaddr *addr = indev->ifa_list;
+ int addrs = 0;
+ int max = 32;
+
+ retry:
+ *_abuf = krealloc(abuf, max * sizeof(*abuf), GFP_KERNEL);
+ if (*_abuf == NULL) {
+ addrs = -ENOMEM;
+ goto out;
+ }
+ abuf = *_abuf;
+
+ read_lock(&dev_base_lock);
+
+ while (addr) {
+ abuf[addrs].type = CKPT_NETDEV_ADDR_IPV4; /* Only IPv4 now */
+ abuf[addrs].inet4_local = htonl(addr->ifa_local);
+ abuf[addrs].inet4_address = htonl(addr->ifa_address);
+ abuf[addrs].inet4_mask = htonl(addr->ifa_mask);
+ abuf[addrs].inet4_broadcast = htonl(addr->ifa_broadcast);
+
+ addr = addr->ifa_next;
+ if (++addrs >= max) {
+ read_unlock(&dev_base_lock);
+ max *= 2;
+ goto retry;
+ }
+ }
+
+ read_unlock(&dev_base_lock);
+ out:
+ if (addrs < 0) {
+ kfree(abuf);
+ *_abuf = NULL;
+ }
+
+ return addrs;
+}
+
+struct ckpt_hdr_netdev *ckpt_netdev_base(struct ckpt_ctx *ctx,
+ struct net_device *dev,
+ struct ckpt_netdev_addr *addrs[])
+{
+ struct ckpt_hdr_netdev *h;
+ int ret;
+
+ h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_NETDEV);
+ if (!h)
+ return ERR_PTR(-ENOMEM);
+
+ ret = ckpt_netdev_hwaddr(dev, h);
+ if (ret < 0)
+ goto out;
+
+ *addrs = NULL;
+ ret = h->inet_addrs = ckpt_netdev_inet_addrs(dev->ip_ptr, addrs);
+ if (ret < 0)
+ goto out;
+
+ if (ckpt_netdev_in_init_netns(ctx, dev))
+ ret = h->netns_ref = 0;
+ else
+ ret = h->netns_ref = checkpoint_obj(ctx, dev->nd_net,
+ CKPT_OBJ_NET_NS);
+ out:
+ if (ret < 0) {
+ ckpt_hdr_put(ctx, h);
+ h = ERR_PTR(ret);
+ kfree(*addrs);
+ }
+
+ return h;
+}
+EXPORT_SYMBOL_GPL(ckpt_netdev_base);
+
+int checkpoint_netdev(struct ckpt_ctx *ctx, void *ptr)
+{
+ struct net_device *dev = (struct net_device *)ptr;
+ int ret;
+
+ if (!dev->netdev_ops->ndo_checkpoint) {
+ ckpt_err(ctx, -ENOSYS,
+ "Device %s does not support checkpoint\n", dev->name);
+ return -ENOSYS;
+ }
+
+ ckpt_debug("checkpointing netdev %s\n", dev->name);
+
+ ret = dev->netdev_ops->ndo_checkpoint(ctx, dev);
+ if (ret < 0)
+ ckpt_err(ctx, ret, "Failed to checkpoint netdev %s: %i\n",
+ dev->name, ret);
+
+ return ret;
+}
+
+int checkpoint_netns(struct ckpt_ctx *ctx, void *ptr)
+{
+ struct net *net = ptr;
+ struct net_device *dev;
+ struct ckpt_hdr_netns *h;
+ int ret;
+
+ h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_NET_NS);
+ if (!h)
+ return -ENOMEM;
+
+ h->this_ref = ckpt_obj_lookup(ctx, net, CKPT_OBJ_NET_NS);
+ BUG_ON(h->this_ref <= 0);
+
+ ret = ckpt_write_obj(ctx, (struct ckpt_hdr *) h);
+ if (ret < 0)
+ goto out;
+
+ for_each_netdev(net, dev) {
+ if (dev->netdev_ops->ndo_checkpoint)
+ ret = checkpoint_obj(ctx, dev, CKPT_OBJ_NETDEV);
+ else if (dev->flags & IFF_UP)
+ ret = -ENOSYS;
+ else
+ /* TODO: There should be a flag to attempt a
+ * checkpoint of downed interfaces, regardless
+ * of whether they support checkpoint or not.
+ */
+ ret = 0;
+ if (ret < 0)
+ break;
+ }
+ out:
+ ckpt_hdr_put(ctx, h);
+
+ return ret;
+}
+
+static int restore_in_addrs(struct ckpt_ctx *ctx,
+ __u32 naddrs,
+ struct net *net,
+ struct net_device *dev)
+{
+ __u32 i;
+ int ret = 0;
+ int len = naddrs * sizeof(struct ckpt_netdev_addr);
+ struct ckpt_netdev_addr *addrs = NULL;
+
+ ret = ckpt_read_payload(ctx, (void **)&addrs, len, CKPT_HDR_BUFFER);
+ if (ret < 0)
+ goto out;
+
+ for (i = 0; i < naddrs; i++) {
+ struct ckpt_netdev_addr *addr = &addrs[i];
+ struct ifreq req;
+ struct sockaddr_in *inaddr;
+
+ if (addr->type != CKPT_NETDEV_ADDR_IPV4) {
+ ret = -EINVAL;
+ ckpt_err(ctx, ret, "Unsupported netdev addr type %i\n",
+ addr->type);
+ break;
+ }
+
+ ckpt_debug("restoring %s: %x/%x/%x\n", dev->name,
+ addr->inet4_address,
+ addr->inet4_mask,
+ addr->inet4_broadcast);
+
+ memcpy(req.ifr_name, dev->name, IFNAMSIZ);
+
+ inaddr = (struct sockaddr_in *)&req.ifr_addr;
+ inaddr->sin_addr.s_addr = ntohl(addr->inet4_address);
+ inaddr->sin_family = AF_INET;
+ ret = __kern_devinet_ioctl(net, SIOCSIFADDR, &req);
+ if (ret < 0) {
+ ckpt_err(ctx, ret, "Failed to set address\n");
+ break;
+ }
+
+ inaddr = (struct sockaddr_in *)&req.ifr_addr;
+ inaddr->sin_addr.s_addr = ntohl(addr->inet4_mask);
+ inaddr->sin_family = AF_INET;
+ ret = __kern_devinet_ioctl(net, SIOCSIFNETMASK, &req);
+ if (ret < 0) {
+ ckpt_err(ctx, ret, "Failed to set netmask\n");
+ break;
+ }
+
+ inaddr = (struct sockaddr_in *)&req.ifr_addr;
+ inaddr->sin_addr.s_addr = ntohl(addr->inet4_broadcast);
+ inaddr->sin_family = AF_INET;
+ ret = __kern_devinet_ioctl(net, SIOCSIFBRDADDR, &req);
+ if (ret < 0) {
+ ckpt_err(ctx, ret, "Failed to set broadcast\n");
+ break;
+ }
+ }
+
+ out:
+ kfree(addrs);
+
+ return ret;
+}
+
+static int veth_new_link_msg(struct sk_buff *skb, void *data)
+{
+ struct nlattr *linkinfo;
+ struct nlattr *linkdata;
+ struct ifinfomsg ifm;
+ int ret = -ENOMEM;
+ struct veth_newlink *d = data;
+
+ linkinfo = nla_nest_start(skb, IFLA_LINKINFO);
+ if (!linkinfo)
+ goto out;
+
+ ret = nla_put_string(skb, IFLA_INFO_KIND, "veth");
+ if (ret)
+ goto out;
+
+ linkdata = nla_nest_start(skb, IFLA_INFO_DATA);
+ if (!linkdata) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = nla_put(skb, VETH_INFO_PEER, sizeof(ifm), &ifm);
+ if (!ret)
+ ret = nla_put_string(skb, IFLA_IFNAME, d->peer);
+
+ nla_nest_end(skb, linkdata);
+ out:
+ nla_nest_end(skb, linkinfo);
+
+ return ret;
+}
+
+static int mvl_new_link_msg(struct sk_buff *skb, void *data)
+{
+ struct mvl_newlink *d = data;
+ struct nlattr *linkinfo;
+ struct nlattr *linkdata;
+ struct net_device *lowerdev;
+ int ret;
+
+ lowerdev = dev_get_by_name(current->nsproxy->net_ns, d->base);
+ if (!lowerdev)
+ return -ENOENT;
+
+ ret = nla_put(skb, IFLA_ADDRESS, ETH_ALEN, d->hwaddr);
+ if (ret)
+ goto out_put;
+
+ ret = nla_put_u32(skb, IFLA_LINK, lowerdev->ifindex);
+ if (ret)
+ goto out_put;
+
+ linkinfo = nla_nest_start(skb, IFLA_LINKINFO);
+ if (!linkinfo) {
+ ret = -ENOMEM;
+ goto out_put;
+ }
+
+ ret = nla_put_string(skb, IFLA_INFO_KIND, "macvlan");
+ if (ret)
+ goto out;
+
+ linkdata = nla_nest_start(skb, IFLA_INFO_DATA);
+ if (!linkdata) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = nla_put_u32(skb, IFLA_MACVLAN_MODE, d->mode);
+ nla_nest_end(skb, linkdata);
+ out:
+ nla_nest_end(skb, linkinfo);
+ out_put:
+ dev_put(lowerdev);
+
+ return ret;
+}
+
+static struct sk_buff *new_link_msg(new_link_fn fn, void *data, char *name)
+{
+ int ret = -ENOMEM;
+ int flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK;
+ struct nlmsghdr *nlh;
+ struct sk_buff *skb;
+ struct ifinfomsg *ifm;
+
+ skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!skb)
+ goto out;
+
+ nlh = nlmsg_put(skb, 0, 0, RTM_NEWLINK, sizeof(*ifm), flags);
+ if (!nlh)
+ goto out;
+
+ ifm = nlmsg_data(nlh);
+ memset(ifm, 0, sizeof(*ifm));
+
+ ret = nla_put_string(skb, IFLA_IFNAME, name);
+ if (ret)
+ goto out;
+
+ ret = fn(skb, data);
+
+ nlmsg_end(skb, nlh);
+
+ out:
+ if (ret < 0) {
+ kfree_skb(skb);
+ skb = ERR_PTR(ret);
+ }
+
+ return skb;
+}
+
+static struct net_device *rtnl_newlink(new_link_fn fn, void *data, char *name)
+{
+ int ret = -ENOMEM;
+ struct socket *rtnl = NULL;
+ struct sk_buff *skb = NULL;
+ struct nlmsghdr *nlh;
+ struct msghdr msg;
+ struct kvec kvec;
+
+ skb = new_link_msg(fn, data, name);
+ if (IS_ERR(skb)) {
+ ckpt_debug("failed to create new link message: %li\n",
+ PTR_ERR(skb));
+ return ERR_PTR(PTR_ERR(skb));
+ }
+
+ memset(&msg, 0, sizeof(msg));
+ kvec.iov_len = skb->len;
+ kvec.iov_base = skb->head;
+
+ rtnl = rtnl_open();
+ if (IS_ERR(rtnl)) {
+ ret = PTR_ERR(rtnl);
+ ckpt_debug("Unable to open rtnetlink socket: %i\n", ret);
+ goto out_noclose;
+ }
+
+ ret = kernel_sendmsg(rtnl, &msg, &kvec, 1, kvec.iov_len);
+ if (ret < 0)
+ goto out;
+ else if (ret != skb->len) {
+ ret = -EIO;
+ goto out;
+ }
+
+ /* Free the send skb to make room for the receive skb */
+ kfree_skb(skb);
+
+ nlh = rtnl_get_response(rtnl, &skb);
+ if (IS_ERR(nlh)) {
+ ret = PTR_ERR(nlh);
+ ckpt_debug("RTNETLINK said: %i\n", ret);
+ }
+ out:
+ rtnl_close(rtnl);
+ out_noclose:
+ kfree_skb(skb);
+
+ if (ret < 0)
+ return ERR_PTR(ret);
+ else
+ return dev_get_by_name(current->nsproxy->net_ns, name);
+}
+
+static int netdev_noop(void *data)
+{
+ return 0;
+}
+
+static int netdev_cleanup(void *data)
+{
+ struct dq_netdev *dq = data;
+
+ dev_put(dq->dev);
+
+ if (dq->ctx->errno) {
+ ckpt_debug("Unregistering netdev %s\n", dq->dev->name);
+ unregister_netdev(dq->dev);
+ }
+
+ return 0;
+}
+
+static struct net_device *restore_veth(struct ckpt_ctx *ctx,
+ struct ckpt_hdr_netdev *h,
+ struct net *net)
+{
+ int ret;
+ char this_name[IFNAMSIZ];
+ char peer_name[IFNAMSIZ];
+ struct net_device *dev;
+ struct net_device *peer;
+ struct ifreq req;
+ struct dq_netdev dq;
+
+ dq.ctx = ctx;
+
+ ret = _ckpt_read_buffer(ctx, this_name, IFNAMSIZ);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ ret = _ckpt_read_buffer(ctx, peer_name, IFNAMSIZ);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ ckpt_debug("restored veth netdev %s:%s\n", this_name, peer_name);
+
+ peer = ckpt_obj_try_fetch(ctx, h->veth.peer_ref, CKPT_OBJ_NETDEV);
+ if (IS_ERR(peer)) {
+ struct veth_newlink veth = {
+ .peer = peer_name,
+ };
+
+ dev = rtnl_newlink(veth_new_link_msg, &veth, this_name);
+ if (IS_ERR(dev))
+ return dev;
+
+ peer = dev_get_by_name(current->nsproxy->net_ns, peer_name);
+ if (!peer) {
+ ret = -EINVAL;
+ goto err_dev;
+ }
+
+ dq.dev = peer;
+ ret = deferqueue_add(ctx->deferqueue, &dq, sizeof(dq),
+ netdev_noop, netdev_cleanup);
+ if (ret)
+ goto err_peer;
+
+ ret = ckpt_obj_insert(ctx, peer, h->veth.peer_ref,
+ CKPT_OBJ_NETDEV);
+ if (ret < 0)
+ /* Can't recall peer dq, so let it cleanup peer */
+ goto err_dev;
+ dev_put(peer);
+
+ dq.dev = dev;
+ ret = deferqueue_add(ctx->deferqueue, &dq, sizeof(dq),
+ netdev_noop, netdev_cleanup);
+ if (ret)
+ /* Can't recall peer dq, so let it cleanup peer */
+ goto err_dev;
+
+ } else {
+ /* We're second: get our dev from the hash */
+ dev = ckpt_obj_fetch(ctx, h->veth.this_ref, CKPT_OBJ_NETDEV);
+ if (IS_ERR(dev))
+ return dev;
+ }
+
+ /* Move to our new netns */
+ rtnl_lock();
+ ret = dev_change_net_namespace(dev, net, dev->name);
+ rtnl_unlock();
+ if (ret < 0)
+ goto out;
+
+ /* Restore MAC address */
+ memcpy(req.ifr_name, dev->name, IFNAMSIZ);
+ memcpy(req.ifr_hwaddr.sa_data, h->hwaddr, sizeof(h->hwaddr));
+ req.ifr_hwaddr.sa_family = ARPHRD_ETHER;
+ ret = __kern_dev_ioctl(net, SIOCSIFHWADDR, &req);
+ out:
+ if (ret)
+ dev = ERR_PTR(ret);
+
+ return dev;
+
+ err_peer:
+ dev_put(peer);
+ unregister_netdev(peer);
+ err_dev:
+ dev_put(dev);
+ unregister_netdev(dev);
+
+ return ERR_PTR(ret);
+}
+
+static struct net_device *restore_lo(struct ckpt_ctx *ctx,
+ struct ckpt_hdr_netdev *h,
+ struct net *net)
+{
+ struct net_device *dev;
+ char name[IFNAMSIZ+1];
+ int ret;
+
+ dev = dev_get_by_name(net, "lo");
+ if (!dev)
+ return ERR_PTR(-EINVAL);
+
+ ret = _ckpt_read_buffer(ctx, name, IFNAMSIZ);
+ if (ret < 0)
+ goto err;
+
+ if (strncmp(dev->name, name, IFNAMSIZ) != 0) {
+ ret = dev_change_name(dev, name);
+ if (ret < 0)
+ goto err;
+ }
+
+ return dev;
+ err:
+ dev_put(dev);
+
+ return ERR_PTR(ret);
+}
+
+static struct net_device *restore_sit(struct ckpt_ctx *ctx,
+ struct ckpt_hdr_netdev *h,
+ struct net *net)
+{
+ /* Don't actually do anything for SIT devices yet */
+ return dev_get_by_name(net, "sit0");
+}
+
+static struct net_device *restore_macvlan(struct ckpt_ctx *ctx,
+ struct ckpt_hdr_netdev *h,
+ struct net *net)
+{
+ struct net_device *dev;
+ struct mvl_newlink mvl = {
+ .mode = h->macvlan.mode,
+ .hwaddr = h->hwaddr,
+ };
+ int ret;
+
+ ret = _ckpt_read_buffer(ctx, mvl.this, IFNAMSIZ);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ ret = _ckpt_read_buffer(ctx, mvl.base, IFNAMSIZ);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ dev = rtnl_newlink(mvl_new_link_msg, &mvl, mvl.this);
+ if (IS_ERR(dev)) {
+ ckpt_err(ctx, PTR_ERR(dev),
+ "Failed to create macvlan device %s:%s",
+ mvl.this, mvl.base);
+ goto out;
+ }
+
+ rtnl_lock();
+ ret = dev_change_net_namespace(dev, net, dev->name);
+ rtnl_unlock();
+
+ if (ret) {
+ ckpt_err(ctx, ret, "Failed to change netns of %s:%s\n",
+ mvl.this, mvl.base);
+ dev_put(dev);
+ unregister_netdev(dev);
+ dev = ERR_PTR(ret);
+ }
+ out:
+ return dev;
+}
+
+typedef struct net_device *(*restore_netdev_fn)(struct ckpt_ctx *,
+ struct ckpt_hdr_netdev *,
+ struct net *);
+
+restore_netdev_fn restore_netdev_functions[] = {
+ restore_lo, /* CKPT_NETDEV_LO */
+ restore_veth, /* CKPT_NETDEV_VETH */
+ restore_sit, /* CKPT_NETDEV_SIT */
+ restore_macvlan, /* CKPT_NETDEV_MACVLAN */
+};
+
+void *restore_netdev(struct ckpt_ctx *ctx)
+{
+ struct ckpt_hdr_netdev *h;
+ struct net_device *dev = NULL;
+ struct ifreq req;
+ struct net *net;
+ int ret;
+ restore_netdev_fn restore_fn = NULL;
+
+ h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_NETDEV);
+ if (IS_ERR(h))
+ return h;
+
+ if (h->netns_ref != 0) {
+ net = ckpt_obj_try_fetch(ctx, h->netns_ref, CKPT_OBJ_NET_NS);
+ if (IS_ERR(net)) {
+ ckpt_debug("failed to get net for %i\n", h->netns_ref);
+ ret = PTR_ERR(net);
+ goto out;
+ }
+ } else
+ net = current->nsproxy->net_ns;
+
+ if (h->type >= CKPT_NETDEV_MAX) {
+ ret = -EINVAL;
+ ckpt_err(ctx, ret, "Invalid netdev type %i\n", h->type);
+ goto out;
+ }
+
+ restore_fn = restore_netdev_functions[h->type];
+
+ dev = restore_fn(ctx, h, net);
+ if (IS_ERR(dev)) {
+ ret = PTR_ERR(dev);
+ ckpt_err(ctx, ret, "Netdev type %i not supported\n", h->type);
+ goto out;
+ }
+
+ /* Restore flags (which will likely bring the interface up) */
+ memcpy(req.ifr_name, dev->name, IFNAMSIZ);
+ req.ifr_flags = h->flags;
+ ret = __kern_dev_ioctl(net, SIOCSIFFLAGS, &req);
+ if (ret < 0)
+ goto out;
+
+ if (h->inet_addrs > 0)
+ ret = restore_in_addrs(ctx, h->inet_addrs, net, dev);
+ out:
+ if (ret) {
+ ckpt_err(ctx, ret, "Failed to restore netdevice\n");
+ if ((h->type == CKPT_NETDEV_VETH) && !IS_ERR(dev))
+ dev_put(dev);
+ dev = ERR_PTR(ret);
+ } else
+ ckpt_debug("restored netdev %s\n", dev->name);
+
+ ckpt_hdr_put(ctx, h);
+
+ return dev;
+}
+
+void *restore_netns(struct ckpt_ctx *ctx)
+{
+ struct ckpt_hdr_netns *h;
+ struct net *net;
+
+ h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_NET_NS);
+ if (IS_ERR(h)) {
+ ckpt_err(ctx, PTR_ERR(h), "failed to read netns\n");
+ return h;
+ }
+
+ if (h->this_ref != 0) {
+ net = copy_net_ns(CLONE_NEWNET, current->nsproxy->net_ns);
+ if (IS_ERR(net))
+ goto out;
+ } else
+ net = current->nsproxy->net_ns;
+ out:
+ ckpt_hdr_put(ctx, h);
+
+ return net;
+}
--
1.6.3.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v21 095/100] c/r: Add rtnl_dellink() helper
[not found] <1272723382-19470-1-git-send-email-orenl@cs.columbia.edu>
` (8 preceding siblings ...)
2010-05-01 14:16 ` [PATCH v21 094/100] c/r: Basic support for network namespaces and devices (v6) Oren Laadan
@ 2010-05-01 14:16 ` Oren Laadan
2010-05-01 14:16 ` [PATCH v21 096/100] c/r: Add checkpoint support for veth devices (v2) Oren Laadan
` (3 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Oren Laadan @ 2010-05-01 14:16 UTC (permalink / raw)
To: Andrew Morton
Cc: containers, linux-kernel, Serge Hallyn, Matt Helsley,
Pavel Emelyanov, Dan Smith, netdev
From: Dan Smith <danms@us.ibm.com>
This is the kernel equivalent of "ip link del $name" and matches the
existing rtnl_newlink() equivalent of "ip link add $name". It factors
out the message creation and dispatch code a little further into
rtnl_do() before adding the new function.
Cc: netdev@vger.kernel.org
Signed-off-by: Dan Smith <danms@us.ibm.com>
---
net/checkpoint_dev.c | 86 +++++++++++++++++++++++++++++++++++++++++--------
1 files changed, 72 insertions(+), 14 deletions(-)
diff --git a/net/checkpoint_dev.c b/net/checkpoint_dev.c
index 34a6bdb..5097011 100644
--- a/net/checkpoint_dev.c
+++ b/net/checkpoint_dev.c
@@ -475,22 +475,49 @@ static struct sk_buff *new_link_msg(new_link_fn fn, void *data, char *name)
return skb;
}
-static struct net_device *rtnl_newlink(new_link_fn fn, void *data, char *name)
+static struct sk_buff *del_link_msg(char *name)
+{
+ int ret = -ENOMEM;
+ int flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK;
+ struct nlmsghdr *nlh;
+ struct sk_buff *skb;
+ struct ifinfomsg *ifm;
+
+ skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!skb)
+ return ERR_PTR(-ENOMEM);
+
+ nlh = nlmsg_put(skb, 0, 0, RTM_DELLINK, sizeof(*ifm), flags);
+ if (!nlh)
+ goto out;
+
+ ifm = nlmsg_data(nlh);
+ memset(ifm, 0, sizeof(*ifm));
+
+ ret = nla_put_string(skb, IFLA_IFNAME, name);
+ if (ret)
+ goto out;
+
+ nlmsg_end(skb, nlh);
+
+ out:
+ if (ret < 0) {
+ kfree_skb(skb);
+ skb = ERR_PTR(ret);
+ }
+
+ return skb;
+}
+
+static int rtnl_do(struct sk_buff *skb)
{
int ret = -ENOMEM;
struct socket *rtnl = NULL;
- struct sk_buff *skb = NULL;
+ struct sk_buff *rskb = NULL;
struct nlmsghdr *nlh;
struct msghdr msg;
struct kvec kvec;
- skb = new_link_msg(fn, data, name);
- if (IS_ERR(skb)) {
- ckpt_debug("failed to create new link message: %li\n",
- PTR_ERR(skb));
- return ERR_PTR(PTR_ERR(skb));
- }
-
memset(&msg, 0, sizeof(msg));
kvec.iov_len = skb->len;
kvec.iov_base = skb->head;
@@ -510,25 +537,56 @@ static struct net_device *rtnl_newlink(new_link_fn fn, void *data, char *name)
goto out;
}
- /* Free the send skb to make room for the receive skb */
- kfree_skb(skb);
-
- nlh = rtnl_get_response(rtnl, &skb);
+ nlh = rtnl_get_response(rtnl, &rskb);
if (IS_ERR(nlh)) {
ret = PTR_ERR(nlh);
ckpt_debug("RTNETLINK said: %i\n", ret);
}
out:
rtnl_close(rtnl);
+ kfree_skb(rskb);
out_noclose:
- kfree_skb(skb);
+ return ret;
+}
+static struct net_device *rtnl_newlink(new_link_fn fn, void *data, char *name)
+{
+ struct sk_buff *skb;
+ int ret;
+
+ skb = new_link_msg(fn, data, name);
+ if (IS_ERR(skb)) {
+ ckpt_debug("failed to create new link message: %li\n",
+ PTR_ERR(skb));
+ return ERR_PTR(PTR_ERR(skb));
+ }
+
+ ret = rtnl_do(skb);
+ kfree_skb(skb);
if (ret < 0)
return ERR_PTR(ret);
else
return dev_get_by_name(current->nsproxy->net_ns, name);
}
+static int rtnl_dellink(char *name)
+{
+ struct sk_buff *skb;
+ int ret;
+
+ skb = del_link_msg(name);
+ if (IS_ERR(skb)) {
+ ckpt_debug("failed to create del link message: %li\n",
+ PTR_ERR(skb));
+ return PTR_ERR(skb);
+ }
+
+ ret = rtnl_do(skb);
+ kfree_skb(skb);
+
+ return ret;
+}
+
static int netdev_noop(void *data)
{
return 0;
--
1.6.3.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v21 096/100] c/r: Add checkpoint support for veth devices (v2)
[not found] <1272723382-19470-1-git-send-email-orenl@cs.columbia.edu>
` (9 preceding siblings ...)
2010-05-01 14:16 ` [PATCH v21 095/100] c/r: Add rtnl_dellink() helper Oren Laadan
@ 2010-05-01 14:16 ` Oren Laadan
2010-05-01 14:16 ` [PATCH v21 097/100] c/r: Add loopback checkpoint support (v2) Oren Laadan
` (2 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Oren Laadan @ 2010-05-01 14:16 UTC (permalink / raw)
To: Andrew Morton
Cc: containers, linux-kernel, Serge Hallyn, Matt Helsley,
Pavel Emelyanov, Dan Smith, netdev
From: Dan Smith <danms@us.ibm.com>
Adds an ndo_checkpoint() handler for veth devices to checkpoint themselves.
Writes out the pairing information, addresses, and initiates a checkpoint
on the peer if the peer won't be reached from another netns. Throws an
error of our peer's netns isn't already in the hash (i.e., a tree leak).
Changelog[v21]
- Unbreak compiling with CONFIG_CHECKPOINT=n or CONFIG_NET_NS=n
- Clean up the error path in restore_veth()
Changes in v2:
- Fix check detecting if peer is in the init netns
Cc: netdev@vger.kernel.org
Signed-off-by: Dan Smith <danms@us.ibm.com>
Acked-by: David S. Miller <davem@davemloft.net>
Acked-by: Serge Hallyn <serue@us.ibm.com>
Acked-by: Oren Laadan <orenl@cs.columbia.edu>
---
drivers/net/veth.c | 76 +++++++++++++++++++++++++++++++++++++++++++
net/checkpoint_dev.c | 87 +++++++++++++++++--------------------------------
2 files changed, 106 insertions(+), 57 deletions(-)
diff --git a/drivers/net/veth.c b/drivers/net/veth.c
index f9f0730..d76b5e0 100644
--- a/drivers/net/veth.c
+++ b/drivers/net/veth.c
@@ -285,6 +285,79 @@ static void veth_dev_free(struct net_device *dev)
free_netdev(dev);
}
+#ifdef CONFIG_NETNS_CHECKPOINT
+#include <linux/checkpoint.h>
+#include <linux/checkpoint_hdr.h>
+
+static int veth_checkpoint(struct ckpt_ctx *ctx, struct net_device *dev)
+{
+ struct ckpt_hdr_netdev *h;
+ struct veth_priv *priv = netdev_priv(dev);
+ struct net_device *peer = priv->peer;
+ struct ckpt_netdev_addr *addrs;
+ int ret;
+ int n;
+
+ if (!peer) {
+ ckpt_err(ctx, -EINVAL, "veth device has no peer!\n");
+ return -EINVAL;
+ }
+
+ h = ckpt_netdev_base(ctx, dev, &addrs);
+ if (IS_ERR(h))
+ return PTR_ERR(h);
+
+ h->type = CKPT_NETDEV_VETH;
+
+ ret = h->veth.this_ref = ckpt_obj_lookup_add(ctx, dev,
+ CKPT_OBJ_NETDEV, &n);
+ if (ret < 0)
+ goto out;
+
+ ret = h->veth.peer_ref = ckpt_obj_lookup_add(ctx, peer,
+ CKPT_OBJ_NETDEV, &n);
+ if (ret < 0)
+ goto out;
+
+ ret = ckpt_write_obj(ctx, (struct ckpt_hdr *)h);
+ if (ret < 0)
+ goto out;
+
+ ret = ckpt_write_buffer(ctx, dev->name, IFNAMSIZ);
+ if (ret < 0)
+ goto out;
+
+ ret = ckpt_write_buffer(ctx, peer->name, IFNAMSIZ);
+ if (ret < 0)
+ goto out;
+
+ if (h->inet_addrs > 0) {
+ int len = (sizeof(struct ckpt_netdev_addr) * h->inet_addrs);
+ ret = ckpt_write_buffer(ctx, addrs, len);
+ if (ret)
+ goto out;
+ }
+
+ /* Only checkpoint peer if we're not going to arrive at it
+ * via another task's netns. Fail if the pipe exits
+ * our container to a netns not already in the hash
+ */
+ if (ckpt_netdev_in_init_netns(ctx, peer))
+ ret = checkpoint_obj(ctx, peer, CKPT_OBJ_NETDEV);
+ else if (!ckpt_obj_lookup(ctx, peer->nd_net, CKPT_OBJ_NET_NS)) {
+ ret = -EINVAL;
+ ckpt_err(ctx, ret,
+ "Peer %s of %s not in checkpointed namespaces\n",
+ peer->name, dev->name);
+ }
+ out:
+ ckpt_hdr_put(ctx, h);
+ kfree(addrs);
+
+ return ret;
+}
+#endif
+
static const struct net_device_ops veth_netdev_ops = {
.ndo_init = veth_dev_init,
.ndo_open = veth_open,
@@ -293,6 +366,9 @@ static const struct net_device_ops veth_netdev_ops = {
.ndo_change_mtu = veth_change_mtu,
.ndo_get_stats = veth_get_stats,
.ndo_set_mac_address = eth_mac_addr,
+#ifdef CONFIG_NETNS_CHECKPOINT
+ .ndo_checkpoint = veth_checkpoint,
+#endif
};
static void veth_setup(struct net_device *dev)
diff --git a/net/checkpoint_dev.c b/net/checkpoint_dev.c
index 5097011..a8e3341 100644
--- a/net/checkpoint_dev.c
+++ b/net/checkpoint_dev.c
@@ -20,11 +20,6 @@
#include <net/net_namespace.h>
#include <net/sch_generic.h>
-struct dq_netdev {
- struct net_device *dev;
- struct ckpt_ctx *ctx;
-};
-
struct veth_newlink {
char *peer;
};
@@ -587,25 +582,6 @@ static int rtnl_dellink(char *name)
return ret;
}
-static int netdev_noop(void *data)
-{
- return 0;
-}
-
-static int netdev_cleanup(void *data)
-{
- struct dq_netdev *dq = data;
-
- dev_put(dq->dev);
-
- if (dq->ctx->errno) {
- ckpt_debug("Unregistering netdev %s\n", dq->dev->name);
- unregister_netdev(dq->dev);
- }
-
- return 0;
-}
-
static struct net_device *restore_veth(struct ckpt_ctx *ctx,
struct ckpt_hdr_netdev *h,
struct net *net)
@@ -616,9 +592,6 @@ static struct net_device *restore_veth(struct ckpt_ctx *ctx,
struct net_device *dev;
struct net_device *peer;
struct ifreq req;
- struct dq_netdev dq;
-
- dq.ctx = ctx;
ret = _ckpt_read_buffer(ctx, this_name, IFNAMSIZ);
if (ret < 0)
@@ -640,37 +613,31 @@ static struct net_device *restore_veth(struct ckpt_ctx *ctx,
if (IS_ERR(dev))
return dev;
+ ret = ckpt_obj_insert(ctx, dev, h->veth.this_ref,
+ CKPT_OBJ_NETDEV);
+ dev_put(dev);
+ if (ret < 0)
+ goto err;
+
peer = dev_get_by_name(current->nsproxy->net_ns, peer_name);
if (!peer) {
ret = -EINVAL;
- goto err_dev;
+ goto err;
}
- dq.dev = peer;
- ret = deferqueue_add(ctx->deferqueue, &dq, sizeof(dq),
- netdev_noop, netdev_cleanup);
- if (ret)
- goto err_peer;
-
ret = ckpt_obj_insert(ctx, peer, h->veth.peer_ref,
CKPT_OBJ_NETDEV);
- if (ret < 0)
- /* Can't recall peer dq, so let it cleanup peer */
- goto err_dev;
dev_put(peer);
-
- dq.dev = dev;
- ret = deferqueue_add(ctx->deferqueue, &dq, sizeof(dq),
- netdev_noop, netdev_cleanup);
- if (ret)
- /* Can't recall peer dq, so let it cleanup peer */
- goto err_dev;
+ if (ret < 0)
+ goto err;
} else {
/* We're second: get our dev from the hash */
dev = ckpt_obj_fetch(ctx, h->veth.this_ref, CKPT_OBJ_NETDEV);
- if (IS_ERR(dev))
- return dev;
+ if (IS_ERR(dev)) {
+ ret = PTR_ERR(dev);
+ goto err;
+ }
}
/* Move to our new netns */
@@ -678,25 +645,31 @@ static struct net_device *restore_veth(struct ckpt_ctx *ctx,
ret = dev_change_net_namespace(dev, net, dev->name);
rtnl_unlock();
if (ret < 0)
- goto out;
+ goto err;
/* Restore MAC address */
memcpy(req.ifr_name, dev->name, IFNAMSIZ);
memcpy(req.ifr_hwaddr.sa_data, h->hwaddr, sizeof(h->hwaddr));
req.ifr_hwaddr.sa_family = ARPHRD_ETHER;
ret = __kern_dev_ioctl(net, SIOCSIFHWADDR, &req);
- out:
- if (ret)
- dev = ERR_PTR(ret);
+ if (ret < 0)
+ goto err;
return dev;
-
- err_peer:
- dev_put(peer);
- unregister_netdev(peer);
- err_dev:
- dev_put(dev);
- unregister_netdev(dev);
+ err:
+ /* Delete from hash to drop reference */
+ ckpt_obj_delete(ctx, h->veth.this_ref, CKPT_OBJ_NETDEV);
+ ckpt_obj_delete(ctx, h->veth.peer_ref, CKPT_OBJ_NETDEV);
+
+ /* This will fail to delete the interface if we get here
+ * because of a failed attempt at setting the hardware
+ * address, since the device has been moved to another netns.
+ * This is not a problem, however, because the death of that
+ * netns will take the device (and its peer) down with it
+ * cleanly.
+ */
+ if (rtnl_dellink(this_name) < 0)
+ ckpt_debug("failed to delete interfaces on error\n");
return ERR_PTR(ret);
}
--
1.6.3.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v21 097/100] c/r: Add loopback checkpoint support (v2)
[not found] <1272723382-19470-1-git-send-email-orenl@cs.columbia.edu>
` (10 preceding siblings ...)
2010-05-01 14:16 ` [PATCH v21 096/100] c/r: Add checkpoint support for veth devices (v2) Oren Laadan
@ 2010-05-01 14:16 ` Oren Laadan
2010-05-01 14:16 ` [PATCH v21 098/100] c/r: Add a checkpoint handler to the 'sit' device Oren Laadan
2010-05-01 14:16 ` [PATCH v21 099/100] c/r: Add checkpoint support to macvlan driver Oren Laadan
13 siblings, 0 replies; 16+ messages in thread
From: Oren Laadan @ 2010-05-01 14:16 UTC (permalink / raw)
To: Andrew Morton
Cc: containers, linux-kernel, Serge Hallyn, Matt Helsley,
Pavel Emelyanov, Dan Smith, netdev
From: Dan Smith <danms@us.ibm.com>
Adds a small ndo_checkpoint() handler for loopback devices to write the
name and addresses like other interfaces.
Changelog[v21]:
- Unbreak compiling with CONFIG_CHECKPOINT=n or CONFIG_NET_NS=n
Changes in v2:
- Add CONFIG_CHECKPOINT around the handler
Cc: netdev@vger.kernel.org
Signed-off-by: Dan Smith <danms@us.ibm.com>
Acked-by: David S. Miller <davem@davemloft.net>
Acked-by: Serge Hallyn <serue@us.ibm.com>
Acked-by: Oren Laadan <orenl@cs.columbia.edu>
---
drivers/net/loopback.c | 45 ++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 42 insertions(+), 3 deletions(-)
diff --git a/drivers/net/loopback.c b/drivers/net/loopback.c
index 72b7949..9a958a8 100644
--- a/drivers/net/loopback.c
+++ b/drivers/net/loopback.c
@@ -155,10 +155,49 @@ static void loopback_dev_free(struct net_device *dev)
free_netdev(dev);
}
+#ifdef CONFIG_NETNS_CHECKPOINT
+#include <linux/checkpoint.h>
+#include <linux/checkpoint_hdr.h>
+
+static int loopback_checkpoint(struct ckpt_ctx *ctx, struct net_device *dev)
+{
+ struct ckpt_hdr_netdev *h;
+ struct ckpt_netdev_addr *addrs;
+ int ret;
+
+ h = ckpt_netdev_base(ctx, dev, &addrs);
+ if (IS_ERR(h))
+ return PTR_ERR(h);
+
+ h->type = CKPT_NETDEV_LO;
+
+ ret = ckpt_write_obj(ctx, (struct ckpt_hdr *)h);
+ if (ret < 0)
+ goto out;
+
+ ret = ckpt_write_buffer(ctx, dev->name, IFNAMSIZ);
+ if (ret < 0)
+ goto out;
+
+ if (h->inet_addrs > 0) {
+ int len = (sizeof(struct ckpt_netdev_addr) * h->inet_addrs);
+ ret = ckpt_write_buffer(ctx, addrs, len);
+ }
+
+ out:
+ ckpt_hdr_put(ctx, h);
+ kfree(addrs);
+
+ return ret;
+}
+#endif
static const struct net_device_ops loopback_ops = {
- .ndo_init = loopback_dev_init,
- .ndo_start_xmit= loopback_xmit,
- .ndo_get_stats = loopback_get_stats,
+ .ndo_init = loopback_dev_init,
+ .ndo_start_xmit = loopback_xmit,
+ .ndo_get_stats = loopback_get_stats,
+#ifdef CONFIG_NETNS_CHECKPOINT
+ .ndo_checkpoint = loopback_checkpoint,
+#endif
};
/*
--
1.6.3.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v21 098/100] c/r: Add a checkpoint handler to the 'sit' device
[not found] <1272723382-19470-1-git-send-email-orenl@cs.columbia.edu>
` (11 preceding siblings ...)
2010-05-01 14:16 ` [PATCH v21 097/100] c/r: Add loopback checkpoint support (v2) Oren Laadan
@ 2010-05-01 14:16 ` Oren Laadan
2010-05-01 14:16 ` [PATCH v21 099/100] c/r: Add checkpoint support to macvlan driver Oren Laadan
13 siblings, 0 replies; 16+ messages in thread
From: Oren Laadan @ 2010-05-01 14:16 UTC (permalink / raw)
To: Andrew Morton
Cc: containers, linux-kernel, Serge Hallyn, Matt Helsley,
Pavel Emelyanov, Dan Smith, netdev
From: Dan Smith <danms@us.ibm.com>
This handler doesn't really do much to checkpoint the device, other
than the minimum required to support the restart process. When we
add IPv6 support to this, then we can fill this out.
This allows us to avoid skipping unsupported interfaces on a normal
system.
Changelog[v21]:
- Do not include checkpoint_hdr.h explicitly
- Unbreak compiling with CONFIG_CHECKPOINT=n or CONFIG_NET_NS=n
Cc: netdev@vger.kernel.org
Signed-off-by: Dan Smith <danms@us.ibm.com>
Acked-by: David S. Miller <davem@davemloft.net>
Acked-by: Serge Hallyn <serue@us.ibm.com>
Acked-by: Oren Laadan <orenl@cs.columbia.edu>
---
net/ipv6/sit.c | 34 ++++++++++++++++++++++++++++++++++
1 files changed, 34 insertions(+), 0 deletions(-)
diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c
index 5abae10..5ecbe56 100644
--- a/net/ipv6/sit.c
+++ b/net/ipv6/sit.c
@@ -1084,11 +1084,45 @@ static int ipip6_tunnel_change_mtu(struct net_device *dev, int new_mtu)
return 0;
}
+#include <linux/checkpoint.h>
+
+#ifdef CONFIG_NETNS_CHECKPOINT
+static int ipip6_checkpoint(struct ckpt_ctx *ctx, struct net_device *dev)
+{
+ struct ckpt_hdr_netdev *h;
+ struct ckpt_netdev_addr *addrs;
+ int ret;
+
+ h = ckpt_netdev_base(ctx, dev, &addrs);
+ if (IS_ERR(h))
+ return PTR_ERR(h);
+
+ h->type = CKPT_NETDEV_SIT;
+
+ ret = ckpt_write_obj(ctx, (struct ckpt_hdr *) h);
+ if (ret < 0)
+ goto out;
+
+ if (h->inet_addrs > 0) {
+ int len = (sizeof(struct ckpt_netdev_addr) * h->inet_addrs);
+ ret = ckpt_write_buffer(ctx, addrs, len);
+ }
+ out:
+ ckpt_hdr_put(ctx, h);
+ kfree(addrs);
+
+ return ret;
+}
+#endif
+
static const struct net_device_ops ipip6_netdev_ops = {
.ndo_uninit = ipip6_tunnel_uninit,
.ndo_start_xmit = ipip6_tunnel_xmit,
.ndo_do_ioctl = ipip6_tunnel_ioctl,
.ndo_change_mtu = ipip6_tunnel_change_mtu,
+#ifdef CONFIG_NETNS_CHECKPOINT
+ .ndo_checkpoint = ipip6_checkpoint,
+#endif
};
static void ipip6_tunnel_setup(struct net_device *dev)
--
1.6.3.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v21 099/100] c/r: Add checkpoint support to macvlan driver
[not found] <1272723382-19470-1-git-send-email-orenl@cs.columbia.edu>
` (12 preceding siblings ...)
2010-05-01 14:16 ` [PATCH v21 098/100] c/r: Add a checkpoint handler to the 'sit' device Oren Laadan
@ 2010-05-01 14:16 ` Oren Laadan
13 siblings, 0 replies; 16+ messages in thread
From: Oren Laadan @ 2010-05-01 14:16 UTC (permalink / raw)
To: Andrew Morton
Cc: containers, linux-kernel, Serge Hallyn, Matt Helsley,
Pavel Emelyanov, Dan Smith, netdev
From: Dan Smith <danms@us.ibm.com>
Changelog[v21]:
- Do not include checkpoint_hdr.h explicitly
- Unbreak compiling with CONFIG_CHECKPOINT=n or CONFIG_NET_NS=n
Cc: netdev@vger.kernel.org
Signed-off-by: Dan Smith <danms@us.ibm.com>
Acked-by: David S. Miller <davem@davemloft.net>
Acked-by: Serge Hallyn <serue@us.ibm.com>
Acked-by: Oren Laadan <oreln@cs.columbia.edu>
---
drivers/net/macvlan.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 48 insertions(+), 0 deletions(-)
diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c
index 40faa36..360828e 100644
--- a/drivers/net/macvlan.c
+++ b/drivers/net/macvlan.c
@@ -455,6 +455,51 @@ static struct net_device_stats *macvlan_dev_get_stats(struct net_device *dev)
return stats;
}
+#include <linux/checkpoint.h>
+
+#ifdef CONFIG_NETNS_CHECKPOINT
+static int macvlan_checkpoint(struct ckpt_ctx *ctx, struct net_device *dev)
+{
+ struct macvlan_dev *vlan = netdev_priv(dev);
+ struct ckpt_hdr_netdev *h;
+ struct ckpt_netdev_addr *addrs;
+ int ret;
+
+ ckpt_debug("Checkpointing macvlan %s:%s\n",
+ dev->name, vlan->lowerdev->name);
+
+ h = ckpt_netdev_base(ctx, dev, &addrs);
+ if (IS_ERR(h))
+ return PTR_ERR(h);
+
+ h->type = CKPT_NETDEV_MACVLAN;
+
+ h->macvlan.mode = vlan->mode;
+
+ ret = ckpt_write_obj(ctx, (struct ckpt_hdr *)h);
+ if (ret < 0)
+ goto out;
+
+ ret = ckpt_write_buffer(ctx, dev->name, IFNAMSIZ);
+ if (ret < 0)
+ goto out;
+
+ ret = ckpt_write_buffer(ctx, vlan->lowerdev->name, IFNAMSIZ);
+ if (ret < 0)
+ goto out;
+
+ if (h->inet_addrs > 0) {
+ int len = (sizeof(struct ckpt_netdev_addr) * h->inet_addrs);
+ ret = ckpt_write_buffer(ctx, addrs, len);
+ }
+ out:
+ ckpt_hdr_put(ctx, h);
+ kfree(addrs);
+
+ return ret;
+}
+#endif
+
static void macvlan_ethtool_get_drvinfo(struct net_device *dev,
struct ethtool_drvinfo *drvinfo)
{
@@ -501,6 +546,9 @@ static const struct net_device_ops macvlan_netdev_ops = {
.ndo_set_multicast_list = macvlan_set_multicast_list,
.ndo_get_stats = macvlan_dev_get_stats,
.ndo_validate_addr = eth_validate_addr,
+#ifdef CONFIG_NETNS_CHECKPOINT
+ .ndo_checkpoint = macvlan_checkpoint,
+#endif
};
static void macvlan_setup(struct net_device *dev)
--
1.6.3.3
^ permalink raw reply related [flat|nested] 16+ messages in thread