[CRIU] [PATCH 2/7] pycriu: add python package

Ruslan Kuprieiev kupruser at gmail.com
Wed Dec 31 04:06:48 PST 2014


pycriu is a python package, that, for now, consists
of rpc module and images package. rpc module contains
data structures for interacting with CRIU RPC.
images package contains methods for loading\dumping
criu images.

See a big comment below in pycriu/images/images.py file.

Signed-off-by: Ruslan Kuprieiev <kupruser at gmail.com>
---
 pycriu/.gitignore         |   3 +
 pycriu/Makefile           |  16 +++
 pycriu/__init__.py        |   2 +
 pycriu/images/.gitignore  |   4 +
 pycriu/images/Makefile    |  26 ++++
 pycriu/images/__init__.py |   3 +
 pycriu/images/images.py   | 298 ++++++++++++++++++++++++++++++++++++++++++++++
 pycriu/images/pb2dict.py  |  90 ++++++++++++++
 8 files changed, 442 insertions(+)
 create mode 100644 pycriu/.gitignore
 create mode 100644 pycriu/Makefile
 create mode 100644 pycriu/__init__.py
 create mode 100644 pycriu/images/.gitignore
 create mode 100644 pycriu/images/Makefile
 create mode 100644 pycriu/images/__init__.py
 create mode 100644 pycriu/images/images.py
 create mode 100644 pycriu/images/pb2dict.py

diff --git a/pycriu/.gitignore b/pycriu/.gitignore
new file mode 100644
index 0000000..8d503da
--- /dev/null
+++ b/pycriu/.gitignore
@@ -0,0 +1,3 @@
+*_pb2.py
+*.pyc
+rpc.py
diff --git a/pycriu/Makefile b/pycriu/Makefile
new file mode 100644
index 0000000..135fe1c
--- /dev/null
+++ b/pycriu/Makefile
@@ -0,0 +1,16 @@
+all: images rpc.py
+
+.PHONY: all images clean
+
+images:
+	$(Q) $(MAKE) -C images all
+
+# rpc_pb2.py doesn't depend on any other file, so
+# it is safe to rename it, dropping ugly _pb2 suffix.
+rpc.py:
+	$(Q) protoc -I=$(SRC_DIR)/protobuf/ --python_out=./ $(SRC_DIR)/protobuf/$(@:.py=.proto)
+	$(Q) mv $(@:.py=_pb2.py) $@
+
+clean:
+	$(Q) $(MAKE) -C images clean
+	$(Q) $(RM) rpc.py *.pyc
diff --git a/pycriu/__init__.py b/pycriu/__init__.py
new file mode 100644
index 0000000..5205973
--- /dev/null
+++ b/pycriu/__init__.py
@@ -0,0 +1,2 @@
+import rpc
+import images
diff --git a/pycriu/images/.gitignore b/pycriu/images/.gitignore
new file mode 100644
index 0000000..234bfe9
--- /dev/null
+++ b/pycriu/images/.gitignore
@@ -0,0 +1,4 @@
+*.pyc
+*_pb2.py
+magic.py
+pb.py
diff --git a/pycriu/images/Makefile b/pycriu/images/Makefile
new file mode 100644
index 0000000..6aa115e
--- /dev/null
+++ b/pycriu/images/Makefile
@@ -0,0 +1,26 @@
+all: pb.py protobuf magic.py
+
+.PHONY: all protobuf clean pb.py
+
+proto := $(filter-out $(SRC_DIR)/protobuf/rpc.proto, $(wildcard $(SRC_DIR)/protobuf/*.proto))
+proto-py-modules := $(foreach m,$(proto),$(subst -,_,$(notdir $(m:.proto=_pb2))))
+
+# We don't need rpc_pb2.py here, as it is not related to the
+# images.
+# Unfortunately, we can't drop ugly _pb2 suffixes here, because
+# some _pb2 files depend on others _pb2 files.
+protobuf:
+	$(Q) protoc -I=$(SRC_DIR)/protobuf --python_out=./ $(proto)
+
+magic.py: $(SRC_DIR)/scripts/magic-gen.py $(SRC_DIR)/include/magic.h
+	$(E) "  GEN  " $@
+	$(Q) python $^ $@
+
+pb.py: protobuf
+	$(Q) echo "# Autogenerated. Do not edit!" > $@
+	$(Q) for m in $(proto-py-modules); do \
+		echo "from $$m import *" >> $@ ;\
+	done
+
+clean:
+	$(Q) $(RM) ./*_pb2.py ./*.pyc magic.py pb.py
diff --git a/pycriu/images/__init__.py b/pycriu/images/__init__.py
new file mode 100644
index 0000000..379943b
--- /dev/null
+++ b/pycriu/images/__init__.py
@@ -0,0 +1,3 @@
+from magic import *
+from images import *
+from pb import *
diff --git a/pycriu/images/images.py b/pycriu/images/images.py
new file mode 100644
index 0000000..a982dde
--- /dev/null
+++ b/pycriu/images/images.py
@@ -0,0 +1,298 @@
+#!/bin/env python
+
+# This file contains methods to deal with criu images.
+#
+# According to http://criu.org/Images, criu images can be described
+# with such IOW:
+# IMAGE_FILE ::= MAGIC { ENTRY }
+# ENTRY      ::= SIZE PAYLOAD [ EXTRA ]
+# PAYLOAD    ::= "message encoded in ProtocolBuffer format"
+# EXTRA      ::= "arbitrary blob, depends on the PAYLOAD contents"
+#
+# MAGIC      ::= "32 bit integer"
+# SIZE       ::= "32 bit integer, equals the PAYLOAD length"
+#
+# In order to convert images to human-readable format, we use dict(json).
+# Using json not only allows us to easily read\write images, but also
+# to use a great variety of tools out there to manipulate them.
+# It also allows us to clearly describe criu images structure.
+#
+# Using dict(json) format, criu images can be described like:
+#
+# {
+#	'magic' : 'FOO',
+#	'entries' : [
+#		entry,
+#		...
+#	]
+# }
+#
+# Entry, in its turn, could be described as:
+#
+# {
+#	'payload' : pb_msg,
+#	'extra' : extra_msg
+# }
+#
+import io
+import google
+import struct
+import os
+import sys
+import json
+import pb2dict
+
+import magic
+from pb import *
+
+# Generic class to handle loading/dumping criu images entries from/to bin
+# format to/from dict(json).
+class entry_handler:
+	"""
+	Generic class to handle loading/dumping criu images
+	entries from/to bin format to/from dict(json).
+	"""
+	def __init__(self, payload, extra_handler=None):
+		"""
+		Sets payload class and extra handler class.
+		"""
+		self.payload		= payload
+		self.extra_handler	= extra_handler
+
+	def load(self, f):
+		"""
+		Convert criu image entries from binary format to dict(json).
+		Takes a file-like object and returnes a list with entries in
+		dict(json) format.
+		"""
+		entries = []
+
+		while True:
+			entry = {}
+
+			# Read payload
+			pb = self.payload()
+			buf = f.read(4)
+			if buf == '':
+				break
+			size, = struct.unpack('i', buf)
+			pb.ParseFromString(f.read(size))
+			entry['payload'] = pb2dict.pb2dict(pb)
+
+			# Read extra
+			if self.extra_handler:
+				entry['extra'] = self.extra_handler.load(f, pb)
+
+			entries.append(entry)
+
+		return entries
+
+	def loads(self, s):
+		"""
+		Same as load(), but takes a string as an argument.
+		"""
+		f = io.BytesIO(s)
+		return self.load(f)
+
+	def dump(self, entries, f):
+		"""
+		Convert criu image entries from dict(json) format to binary.
+		Takes a list of entries and a file-like object to write entries
+		in binary format to.
+		"""
+		for entry in entries:
+			# Write payload
+			pb = self.payload()
+			pb2dict.dict2pb(entry['payload'], pb)
+			pb_str = pb.SerializeToString()
+			size = len(pb_str)
+			f.write(struct.pack('i', size))
+			f.write(pb_str)
+
+			# Write extra
+			if self.extra_handler:
+				self.extra_handler.dump(entry['extra'], f, pb)
+
+	def dumps(self, entries):
+		"""
+		Same as dump(), but doesn't take file-like object and just
+		returns a string.
+		"""
+		f = io.BytesIO('')
+		self.dump(entries, f)
+		return f.read()
+
+# Some custom extra handlers
+
+class pagemap_extra_handler:
+	def load(self, f, pload):
+		array = []
+
+		while True:
+			pb = pagemap_entry()
+			buf = f.read(4)
+			if buf == '':
+				break
+			size, = struct.unpack('i', buf)
+			pb.ParseFromString(f.read(size))
+			array.append(pb2dict.pb2dict(pb))
+
+		return array
+
+	def dump(self, extra, f, pload):
+		for item in extra:
+			pb = pagemap_entry()
+			pb2dict.dict2pb(item, pb)
+			pb_str = pb.SerializeToString()
+			size = len(pb_str)
+			f.write(struct.pack('i', size))
+			f.write(pb_str)
+
+# In following handlers we use base64 encoding
+# to store binary data. Even though, the nature
+# of base64 is that it increases the total size,
+# it doesn't really matter, because our images
+# do not store big amounts of binary data. They
+# are negligible comparing to pages size.
+class pipes_data_extra_handler:
+	def load(self, f, pload):
+		size = pload.bytes
+		data = f.read(size)
+		return data.encode('base64')
+
+	def dump(self, extra, f, pload):
+		data = extra.decode('base64')
+		f.write(data)
+
+class sk_queues_extra_handler:
+	def load(self, f, pb):
+		size = pload.length
+		data = f.read(size)
+		return data.encode('base64')
+
+	def dump(self, extra, f, pb):
+		data = extra.decode('base64')
+		f.write(data)
+
+class ghost_file_extra_handler:
+	def load(self, f, pb):
+		data = f.read()
+		return data.encode('base64')
+
+	def dump(self, extra, f, pb):
+		data = extra.decode('base64')
+		f.write(data)
+
+handlers = {
+	'INVENTORY'		: entry_handler(inventory_entry),
+	'CORE'			: entry_handler(core_entry),
+	'IDS'			: entry_handler(task_kobj_ids_entry),
+	'CREDS'			: entry_handler(creds_entry),
+	'UTSNS'			: entry_handler(utsns_entry),
+	'IPC_VAR'		: entry_handler(ipc_var_entry),
+	'FS'			: entry_handler(fs_entry),
+	'GHOST_FILE'		: entry_handler(ghost_file_entry, ghost_file_extra_handler()),
+	'MM'			: entry_handler(mm_entry),
+	'CGROUP'		: entry_handler(cgroup_entry),
+	'TCP_STREAM'		: entry_handler(tcp_stream_entry),
+	'STATS'			: entry_handler(stats_entry),
+	'PAGEMAP'		: entry_handler(pagemap_head, pagemap_extra_handler()),
+	'PSTREE'		: entry_handler(pstree_entry),
+	'REG_FILES'		: entry_handler(reg_file_entry),
+	'NS_FILES'		: entry_handler(ns_file_entry),
+	'EVENTFD_FILE'		: entry_handler(eventfd_file_entry),
+	'EVENTPOLL_FILE'	: entry_handler(eventpoll_file_entry),
+	'EVENTPOLL_TFD'		: entry_handler(eventpoll_tfd_entry),
+	'SIGNALFD'		: entry_handler(signalfd_entry),
+	'TIMERFD'		: entry_handler(timerfd_entry),
+	'INOTIFY_FILE'		: entry_handler(inotify_file_entry),
+	'INOTIFY_WD'		: entry_handler(inotify_wd_entry),
+	'FANOTIFY_FILE'		: entry_handler(fanotify_file_entry),
+	'FANOTIFY_MARK'		: entry_handler(fanotify_mark_entry),
+	'VMAS'			: entry_handler(vma_entry),
+	'PIPES'			: entry_handler(pipe_entry),
+	'FIFO'			: entry_handler(fifo_entry),
+	'SIGACT'		: entry_handler(sa_entry),
+	'NETLINK_SK'		: entry_handler(netlink_sk_entry),
+	'REMAP_FPATH'		: entry_handler(remap_file_path_entry),
+	'MNTS'			: entry_handler(mnt_entry),
+	'TTY_FILES'		: entry_handler(tty_file_entry),
+	'TTY_INFO'		: entry_handler(tty_info_entry),
+	'RLIMIT'		: entry_handler(rlimit_entry),
+	'TUNFILE'		: entry_handler(tunfile_entry),
+	'EXT_FILES'		: entry_handler(ext_file_entry),
+	'IRMAP_CACHE'		: entry_handler(irmap_cache_entry),
+	'FILE_LOCKS'		: entry_handler(file_lock_entry),
+	'FDINFO'		: entry_handler(fdinfo_entry),
+	'UNIXSK'		: entry_handler(unix_sk_entry),
+	'INETSK'		: entry_handler(inet_sk_entry),
+	'PACKETSK'		: entry_handler(packet_sock_entry),
+	'ITIMERS'		: entry_handler(itimer_entry),
+	'POSIX_TIMERS'		: entry_handler(posix_timer_entry),
+	'NETDEV'		: entry_handler(net_device_entry),
+	'PIPES_DATA'		: entry_handler(pipe_data_entry, pipes_data_extra_handler()),
+	'FIFO_DATA'		: entry_handler(pipe_data_entry, pipes_data_extra_handler()),
+	'SK_QUEUES'		: entry_handler(sk_packet_entry, sk_queues_extra_handler()),
+	'IPCNS_SHM'		: entry_handler(ipc_shm_entry),
+	'IPCNS_SEM'		: entry_handler(ipc_sem_entry),
+	'IPCNS_MSG'		: entry_handler(ipc_msg_entry)
+	}
+
+def load(f):
+	"""
+	Convert criu image from binary format to dict(json).
+	Takes a file-like object to read criu image from.
+	Returns criu image in dict(json) format.
+	"""
+	image = {}
+
+	img_magic, = struct.unpack('i', f.read(4))
+
+	try:
+		m = magic.by_val[img_magic]
+	except:
+		raise Exception("Unknown magic "+str(img_magic))
+
+	try:
+		handler = handlers[m]
+	except:
+		raise Exception("No handler found for image with such magic "+m)
+
+	image['magic']		= m
+	image['entries']	= handler.load(f)
+
+	return image
+
+def loads(s):
+	"""
+	Same as load(), but takes a string.
+	"""
+	f = io.BytesIO(s)
+	return load(f)
+
+def dump(img, f):
+	"""
+	Convert criu image from dict(json) format to binary.
+	Takes an image in dict(json) format and file-like
+	object to write to.
+	"""
+	m = img['magic']
+	magic_val = magic.by_name[img['magic']]
+
+	f.write(struct.pack('i', magic_val))
+
+	try:
+		handler = handlers[m]
+	except:
+		raise Exception("No handler found for image with such magic")
+
+	handler.dump(img['entries'], f)
+
+def dumps(img):
+	"""
+	Same as dump(), but takes only an image and returns
+	a string.
+	"""
+	f = io.BytesIO('')
+	dump(img, f)
+	return f.getvalue()
diff --git a/pycriu/images/pb2dict.py b/pycriu/images/pb2dict.py
new file mode 100644
index 0000000..3887c9d
--- /dev/null
+++ b/pycriu/images/pb2dict.py
@@ -0,0 +1,90 @@
+import google
+import io
+
+# pb2dict and dict2pb are using protobuf text format to
+# convert protobuf msgs to/from dictionary.
+
+def pb2dict(pb):
+	"""
+	Convert protobuf msg to dictionary.
+	Takes a protobuf message and returns a dict.
+	"""
+	pb_text = io.BytesIO('')
+	google.protobuf.text_format.PrintMessage(pb, pb_text)
+	pb_text.seek(0)
+	return _text2dict(pb_text)
+
+def _text2dict(pb_text):
+	"""
+	Convert protobuf text format msg to dict
+	Takes a protobuf message in text format and
+	returns a dict.
+	"""
+	d = {}
+	while True:
+		s = pb_text.readline()
+		s.strip()
+		if s == '' or '}' in s:
+			break
+
+		name, value = s.split()
+		if value == '{':
+			value = _text2dict(pb_text)
+		elif name.endswith(':'):
+			name = name[:-1]
+		else:
+			raise Exception("Unknown format" + s)
+
+		if d.get(name):
+			if not isinstance(d[name], list):
+				d[name] = [d[name]]
+			d[name].append(value)
+		else:
+			d[name] = value
+
+	return d
+
+def dict2pb(d, pb):
+	"""
+	Convert dictionary to protobuf msg.
+	Takes dict and protobuf message to be merged into.
+	"""
+	pb_text = io.BytesIO('')
+	_dict2text(d, pb_text, 0)
+	pb_text.seek(0)
+	s = pb_text.read()
+	google.protobuf.text_format.Merge(s, pb)
+
+def _write_struct(name, text, indent, inside):
+	"""
+	Convert "inside" dict to protobuf text format
+	wrap it inside block named "name" and write
+	it to "text".
+	"""
+	text.write(indent*" " + name.encode() + " {\n")
+	_dict2text(inside, text, indent+2)
+	text.write(indent*" " + "}\n")
+
+def _write_field(name, value, text, indent):
+	"""
+	Write "name: value" to "text".
+	"""
+	text.write(indent*" " + name.encode() + ": " + value.encode() + "\n")
+
+def _dict2text(d, pb_text, indent):
+	"""
+	Convert dict to protobuf text format.
+	Takes dict, protobuf message in text format and a number
+	of spaces to be put before each field.
+	"""
+	for name, value in d.iteritems():
+		if isinstance(value, unicode):
+			_write_field(name, value, pb_text, indent)
+		elif isinstance(value, list):
+			for x in value:
+				if isinstance(x, dict):
+					_write_struct(name, pb_text, indent, x)
+				else:
+					_write_field(name, x, pb_text, indent)
+		else:
+			_write_struct(name, pb_text, indent, value)
-- 
2.1.0



More information about the CRIU mailing list