Browse Source

YT-19430: Add arrow writer

Add arrow writer
nadya02 1 year ago
parent
commit
f3351138d2

+ 1355 - 0
contrib/libs/backtrace/macho.c

@@ -0,0 +1,1355 @@
+/* elf.c -- Get debug data from a Mach-O file for backtraces.
+   Copyright (C) 2020-2021 Free Software Foundation, Inc.
+   Written by Ian Lance Taylor, Google.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    (1) Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+
+    (2) Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in
+    the documentation and/or other materials provided with the
+    distribution.
+
+    (3) The name of the author may not be used to
+    endorse or promote products derived from this software without
+    specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.  */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_MACH_O_DYLD_H
+#include <mach-o/dyld.h>
+#endif
+
+#include "backtrace.h"
+#include "internal.h"
+
+/* Mach-O file header for a 32-bit executable.  */
+
+struct macho_header_32
+{
+  uint32_t magic;	/* Magic number (MACH_O_MAGIC_32) */
+  uint32_t cputype;	/* CPU type */
+  uint32_t cpusubtype;	/* CPU subtype */
+  uint32_t filetype;	/* Type of file (object, executable) */
+  uint32_t ncmds;	/* Number of load commands */
+  uint32_t sizeofcmds;	/* Total size of load commands */
+  uint32_t flags;	/* Flags for special features */
+};
+
+/* Mach-O file header for a 64-bit executable.  */
+
+struct macho_header_64
+{
+  uint32_t magic;	/* Magic number (MACH_O_MAGIC_64) */
+  uint32_t cputype;	/* CPU type */
+  uint32_t cpusubtype;	/* CPU subtype */
+  uint32_t filetype;	/* Type of file (object, executable) */
+  uint32_t ncmds;	/* Number of load commands */
+  uint32_t sizeofcmds;	/* Total size of load commands */
+  uint32_t flags;	/* Flags for special features */
+  uint32_t reserved;	/* Reserved */
+};
+
+/* Mach-O file header for a fat executable.  */
+
+struct macho_header_fat
+{
+  uint32_t magic;	/* Magic number (MACH_O_MH_(MAGIC|CIGAM)_FAT(_64)?) */
+  uint32_t nfat_arch;   /* Number of components */
+};
+
+/* Values for the header magic field.  */
+
+#define MACH_O_MH_MAGIC_32	0xfeedface
+#define MACH_O_MH_MAGIC_64	0xfeedfacf
+#define MACH_O_MH_MAGIC_FAT	0xcafebabe
+#define MACH_O_MH_CIGAM_FAT	0xbebafeca
+#define MACH_O_MH_MAGIC_FAT_64	0xcafebabf
+#define MACH_O_MH_CIGAM_FAT_64	0xbfbafeca
+
+/* Value for the header filetype field.  */
+
+#define MACH_O_MH_EXECUTE	0x02
+#define MACH_O_MH_DYLIB		0x06
+#define MACH_O_MH_DSYM		0x0a
+
+/* A component of a fat file.  A fat file starts with a
+   macho_header_fat followed by nfat_arch instances of this
+   struct.  */
+
+struct macho_fat_arch
+{
+  uint32_t cputype;	/* CPU type */
+  uint32_t cpusubtype;	/* CPU subtype */
+  uint32_t offset;	/* File offset of this entry */
+  uint32_t size;	/* Size of this entry */
+  uint32_t align;	/* Alignment of this entry */
+};
+
+/* A component of a 64-bit fat file.  This is used if the magic field
+   is MAGIC_FAT_64.  This is only used when some file size or file
+   offset is too large to represent in the 32-bit format.  */
+
+struct macho_fat_arch_64
+{
+  uint32_t cputype;	/* CPU type */
+  uint32_t cpusubtype;	/* CPU subtype */
+  uint64_t offset;	/* File offset of this entry */
+  uint64_t size;	/* Size of this entry */
+  uint32_t align;	/* Alignment of this entry */
+  uint32_t reserved;	/* Reserved */
+};
+
+/* Values for the fat_arch cputype field (and the header cputype
+   field).  */
+
+#define MACH_O_CPU_ARCH_ABI64 0x01000000
+
+#define MACH_O_CPU_TYPE_X86 7
+#define MACH_O_CPU_TYPE_ARM 12
+#define MACH_O_CPU_TYPE_PPC 18
+
+#define MACH_O_CPU_TYPE_X86_64 (MACH_O_CPU_TYPE_X86 | MACH_O_CPU_ARCH_ABI64)
+#define MACH_O_CPU_TYPE_ARM64  (MACH_O_CPU_TYPE_ARM | MACH_O_CPU_ARCH_ABI64)
+#define MACH_O_CPU_TYPE_PPC64  (MACH_O_CPU_TYPE_PPC | MACH_O_CPU_ARCH_ABI64)
+
+/* The header of a load command.  */
+
+struct macho_load_command
+{
+  uint32_t cmd;		/* The type of load command */
+  uint32_t cmdsize;	/* Size in bytes of the entire command */
+};
+
+/* Values for the load_command cmd field.  */
+
+#define MACH_O_LC_SEGMENT	0x01
+#define MACH_O_LC_SYMTAB	0x02
+#define MACH_O_LC_SEGMENT_64	0x19
+#define MACH_O_LC_UUID		0x1b
+
+/* The length of a section of segment name.  */
+
+#define MACH_O_NAMELEN (16)
+
+/* LC_SEGMENT load command.  */
+
+struct macho_segment_command
+{
+  uint32_t cmd;			/* The type of load command (LC_SEGMENT) */
+  uint32_t cmdsize;		/* Size in bytes of the entire command */
+  char segname[MACH_O_NAMELEN];	/* Segment name */
+  uint32_t vmaddr;		/* Virtual memory address */
+  uint32_t vmsize;		/* Virtual memory size */
+  uint32_t fileoff;		/* Offset of data to be mapped */
+  uint32_t filesize;		/* Size of data in file */
+  uint32_t maxprot;		/* Maximum permitted virtual protection */
+  uint32_t initprot;		/* Initial virtual memory protection */
+  uint32_t nsects;		/* Number of sections in this segment */
+  uint32_t flags;		/* Flags */
+};
+
+/* LC_SEGMENT_64 load command.  */
+
+struct macho_segment_64_command
+{
+  uint32_t cmd;			/* The type of load command (LC_SEGMENT) */
+  uint32_t cmdsize;		/* Size in bytes of the entire command */
+  char segname[MACH_O_NAMELEN];	/* Segment name */
+  uint64_t vmaddr;		/* Virtual memory address */
+  uint64_t vmsize;		/* Virtual memory size */
+  uint64_t fileoff;		/* Offset of data to be mapped */
+  uint64_t filesize;		/* Size of data in file */
+  uint32_t maxprot;		/* Maximum permitted virtual protection */
+  uint32_t initprot;		/* Initial virtual memory protection */
+  uint32_t nsects;		/* Number of sections in this segment */
+  uint32_t flags;		/* Flags */
+};
+
+/* LC_SYMTAB load command.  */
+
+struct macho_symtab_command
+{
+  uint32_t cmd;		/* The type of load command (LC_SEGMENT) */
+  uint32_t cmdsize;	/* Size in bytes of the entire command */
+  uint32_t symoff;	/* File offset of symbol table */
+  uint32_t nsyms;	/* Number of symbols */
+  uint32_t stroff;	/* File offset of string table */
+  uint32_t strsize;	/* String table size */
+};
+
+/* The length of a Mach-O uuid.  */
+
+#define MACH_O_UUID_LEN (16)
+
+/* LC_UUID load command.  */
+
+struct macho_uuid_command
+{
+  uint32_t cmd;				/* Type of load command (LC_UUID) */
+  uint32_t cmdsize;			/* Size in bytes of command */
+  unsigned char uuid[MACH_O_UUID_LEN];	/* UUID */
+};
+
+/* 32-bit section header within a LC_SEGMENT segment.  */
+
+struct macho_section
+{
+  char sectname[MACH_O_NAMELEN];	/* Section name */
+  char segment[MACH_O_NAMELEN];		/* Segment of this section */
+  uint32_t addr;			/* Address in memory */
+  uint32_t size;			/* Section size */
+  uint32_t offset;			/* File offset */
+  uint32_t align;			/* Log2 of section alignment */
+  uint32_t reloff;			/* File offset of relocations */
+  uint32_t nreloc;			/* Number of relocs for this section */
+  uint32_t flags;			/* Flags */
+  uint32_t reserved1;
+  uint32_t reserved2;
+};
+
+/* 64-bit section header within a LC_SEGMENT_64 segment.   */
+
+struct macho_section_64
+{
+  char sectname[MACH_O_NAMELEN];	/* Section name */
+  char segment[MACH_O_NAMELEN];		/* Segment of this section */
+  uint64_t addr;			/* Address in memory */
+  uint64_t size;			/* Section size */
+  uint32_t offset;			/* File offset */
+  uint32_t align;			/* Log2 of section alignment */
+  uint32_t reloff;			/* File offset of section relocations */
+  uint32_t nreloc;			/* Number of relocs for this section */
+  uint32_t flags;			/* Flags */
+  uint32_t reserved1;
+  uint32_t reserved2;
+  uint32_t reserved3;
+};
+
+/* 32-bit symbol data.  */
+
+struct macho_nlist
+{
+  uint32_t n_strx;	/* Index of name in string table */
+  uint8_t n_type;	/* Type flag */
+  uint8_t n_sect;	/* Section number */
+  uint16_t n_desc;	/* Stabs description field */
+  uint32_t n_value;	/* Value */
+};
+
+/* 64-bit symbol data.  */
+
+struct macho_nlist_64
+{
+  uint32_t n_strx;	/* Index of name in string table */
+  uint8_t n_type;	/* Type flag */
+  uint8_t n_sect;	/* Section number */
+  uint16_t n_desc;	/* Stabs description field */
+  uint64_t n_value;	/* Value */
+};
+
+/* Value found in nlist n_type field.  */
+
+#define MACH_O_N_EXT	0x01	/* Extern symbol */
+#define MACH_O_N_ABS	0x02	/* Absolute symbol */
+#define MACH_O_N_SECT	0x0e	/* Defined in section */
+
+#define MACH_O_N_TYPE	0x0e	/* Mask for type bits */
+#define MACH_O_N_STAB	0xe0	/* Stabs debugging symbol */
+
+/* Information we keep for a Mach-O symbol.  */
+
+struct macho_symbol
+{
+  const char *name;	/* Symbol name */
+  uintptr_t address;	/* Symbol address */
+};
+
+/* Information to pass to macho_syminfo.  */
+
+struct macho_syminfo_data
+{
+  struct macho_syminfo_data *next;	/* Next module */
+  struct macho_symbol *symbols;		/* Symbols sorted by address */
+  size_t count;				/* Number of symbols */
+};
+
+/* Names of sections, indexed by enum dwarf_section in internal.h.  */
+
+static const char * const dwarf_section_names[DEBUG_MAX] =
+{
+  "__debug_info",
+  "__debug_line",
+  "__debug_abbrev",
+  "__debug_ranges",
+  "__debug_str",
+  "", /* DEBUG_ADDR */
+  "__debug_str_offs",
+  "", /* DEBUG_LINE_STR */
+  "__debug_rnglists"
+};
+
+/* Forward declaration.  */
+
+static int macho_add (struct backtrace_state *, const char *, int, off_t,
+		      const unsigned char *, uintptr_t, int,
+		      backtrace_error_callback, void *, fileline *, int *);
+
+/* A dummy callback function used when we can't find any debug info.  */
+
+static int
+macho_nodebug (struct backtrace_state *state ATTRIBUTE_UNUSED,
+	       uintptr_t pc ATTRIBUTE_UNUSED,
+	       backtrace_full_callback callback ATTRIBUTE_UNUSED,
+	       backtrace_error_callback error_callback, void *data)
+{
+  error_callback (data, "no debug info in Mach-O executable", -1);
+  return 0;
+}
+
+/* A dummy callback function used when we can't find a symbol
+   table.  */
+
+static void
+macho_nosyms (struct backtrace_state *state ATTRIBUTE_UNUSED,
+	      uintptr_t addr ATTRIBUTE_UNUSED,
+	      backtrace_syminfo_callback callback ATTRIBUTE_UNUSED,
+	      backtrace_error_callback error_callback, void *data)
+{
+  error_callback (data, "no symbol table in Mach-O executable", -1);
+}
+
+/* Add a single DWARF section to DWARF_SECTIONS, if we need the
+   section.  Returns 1 on success, 0 on failure.  */
+
+static int
+macho_add_dwarf_section (struct backtrace_state *state, int descriptor,
+			 const char *sectname, uint32_t offset, uint64_t size,
+			 backtrace_error_callback error_callback, void *data,
+			 struct dwarf_sections *dwarf_sections)
+{
+  int i;
+
+  for (i = 0; i < (int) DEBUG_MAX; ++i)
+    {
+      if (dwarf_section_names[i][0] != '\0'
+	  && strncmp (sectname, dwarf_section_names[i], MACH_O_NAMELEN) == 0)
+	{
+	  struct backtrace_view section_view;
+
+	  /* FIXME: Perhaps it would be better to try to use a single
+	     view to read all the DWARF data, as we try to do for
+	     ELF.  */
+
+	  if (!backtrace_get_view (state, descriptor, offset, size,
+				   error_callback, data, &section_view))
+	    return 0;
+	  dwarf_sections->data[i] = (const unsigned char *) section_view.data;
+	  dwarf_sections->size[i] = size;
+	  break;
+	}
+    }
+  return 1;
+}
+
+/* Collect DWARF sections from a DWARF segment.  Returns 1 on success,
+   0 on failure.  */
+
+static int
+macho_add_dwarf_segment (struct backtrace_state *state, int descriptor,
+			 off_t offset, unsigned int cmd, const char *psecs,
+			 size_t sizesecs, unsigned int nsects,
+			 backtrace_error_callback error_callback, void *data,
+			 struct dwarf_sections *dwarf_sections)
+{
+  size_t sec_header_size;
+  size_t secoffset;
+  unsigned int i;
+
+  switch (cmd)
+    {
+    case MACH_O_LC_SEGMENT:
+      sec_header_size = sizeof (struct macho_section);
+      break;
+    case MACH_O_LC_SEGMENT_64:
+      sec_header_size = sizeof (struct macho_section_64);
+      break;
+    default:
+      abort ();
+    }
+
+  secoffset = 0;
+  for (i = 0; i < nsects; ++i)
+    {
+      if (secoffset + sec_header_size > sizesecs)
+	{
+	  error_callback (data, "section overflow withing segment", 0);
+	  return 0;
+	}
+
+      switch (cmd)
+	{
+	case MACH_O_LC_SEGMENT:
+	  {
+	    struct macho_section section;
+
+	    memcpy (&section, psecs + secoffset, sizeof section);
+	    macho_add_dwarf_section (state, descriptor, section.sectname,
+				     offset + section.offset, section.size,
+				     error_callback, data, dwarf_sections);
+	  }
+	  break;
+
+	case MACH_O_LC_SEGMENT_64:
+	  {
+	    struct macho_section_64 section;
+
+	    memcpy (&section, psecs + secoffset, sizeof section);
+	    macho_add_dwarf_section (state, descriptor, section.sectname,
+				     offset + section.offset, section.size,
+				     error_callback, data, dwarf_sections);
+	  }
+	  break;
+
+	default:
+	  abort ();
+	}
+
+      secoffset += sec_header_size;
+    }
+
+  return 1;
+}
+
+/* Compare struct macho_symbol for qsort.  */
+
+static int
+macho_symbol_compare (const void *v1, const void *v2)
+{
+  const struct macho_symbol *m1 = (const struct macho_symbol *) v1;
+  const struct macho_symbol *m2 = (const struct macho_symbol *) v2;
+
+  if (m1->address < m2->address)
+    return -1;
+  else if (m1->address > m2->address)
+    return 1;
+  else
+    return 0;
+}
+
+/* Compare an address against a macho_symbol for bsearch.  We allocate
+   one extra entry in the array so that this can safely look at the
+   next entry.  */
+
+static int
+macho_symbol_search (const void *vkey, const void *ventry)
+{
+  const uintptr_t *key = (const uintptr_t *) vkey;
+  const struct macho_symbol *entry = (const struct macho_symbol *) ventry;
+  uintptr_t addr;
+
+  addr = *key;
+  if (addr < entry->address)
+    return -1;
+  else if (entry->name[0] == '\0'
+	   && entry->address == ~(uintptr_t) 0)
+    return -1;
+  else if ((entry + 1)->name[0] == '\0'
+	   && (entry + 1)->address == ~(uintptr_t) 0)
+    return -1;
+  else if (addr >= (entry + 1)->address)
+    return 1;
+  else
+    return 0;
+}
+
+/* Return whether the symbol type field indicates a symbol table entry
+   that we care about: a function or data symbol.  */
+
+static int
+macho_defined_symbol (uint8_t type)
+{
+  if ((type & MACH_O_N_STAB) != 0)
+    return 0;
+  if ((type & MACH_O_N_EXT) != 0)
+    return 0;
+  switch (type & MACH_O_N_TYPE)
+    {
+    case MACH_O_N_ABS:
+      return 1;
+    case MACH_O_N_SECT:
+      return 1;
+    default:
+      return 0;
+    }
+}
+
+/* Add symbol table information for a Mach-O file.  */
+
+static int
+macho_add_symtab (struct backtrace_state *state, int descriptor,
+		  uintptr_t base_address, int is_64,
+		  off_t symoff, unsigned int nsyms, off_t stroff,
+		  unsigned int strsize,
+		  backtrace_error_callback error_callback, void *data)
+{
+  size_t symsize;
+  struct backtrace_view sym_view;
+  int sym_view_valid;
+  struct backtrace_view str_view;
+  int str_view_valid;
+  size_t ndefs;
+  size_t symtaboff;
+  unsigned int i;
+  size_t macho_symbol_size;
+  struct macho_symbol *macho_symbols;
+  unsigned int j;
+  struct macho_syminfo_data *sdata;
+
+  sym_view_valid = 0;
+  str_view_valid = 0;
+  macho_symbol_size = 0;
+  macho_symbols = NULL;
+
+  if (is_64)
+    symsize = sizeof (struct macho_nlist_64);
+  else
+    symsize = sizeof (struct macho_nlist);
+
+  if (!backtrace_get_view (state, descriptor, symoff, nsyms * symsize,
+			   error_callback, data, &sym_view))
+    goto fail;
+  sym_view_valid = 1;
+
+  if (!backtrace_get_view (state, descriptor, stroff, strsize,
+			   error_callback, data, &str_view))
+    return 0;
+  str_view_valid = 1;
+
+  ndefs = 0;
+  symtaboff = 0;
+  for (i = 0; i < nsyms; ++i, symtaboff += symsize)
+    {
+      if (is_64)
+	{
+	  struct macho_nlist_64 nlist;
+
+	  memcpy (&nlist, (const char *) sym_view.data + symtaboff,
+		  sizeof nlist);
+	  if (macho_defined_symbol (nlist.n_type))
+	    ++ndefs;
+	}
+      else
+	{
+	  struct macho_nlist nlist;
+
+	  memcpy (&nlist, (const char *) sym_view.data + symtaboff,
+		  sizeof nlist);
+	  if (macho_defined_symbol (nlist.n_type))
+	    ++ndefs;
+	}
+    }
+
+  /* Add 1 to ndefs to make room for a sentinel.  */
+  macho_symbol_size = (ndefs + 1) * sizeof (struct macho_symbol);
+  macho_symbols = ((struct macho_symbol *)
+		   backtrace_alloc (state, macho_symbol_size, error_callback,
+				    data));
+  if (macho_symbols == NULL)
+    goto fail;
+
+  j = 0;
+  symtaboff = 0;
+  for (i = 0; i < nsyms; ++i, symtaboff += symsize)
+    {
+      uint32_t strx;
+      uint64_t value;
+      const char *name;
+
+      strx = 0;
+      value = 0;
+      if (is_64)
+	{
+	  struct macho_nlist_64 nlist;
+
+	  memcpy (&nlist, (const char *) sym_view.data + symtaboff,
+		  sizeof nlist);
+	  if (!macho_defined_symbol (nlist.n_type))
+	    continue;
+
+	  strx = nlist.n_strx;
+	  value = nlist.n_value;
+	}
+      else
+	{
+	  struct macho_nlist nlist;
+
+	  memcpy (&nlist, (const char *) sym_view.data + symtaboff,
+		  sizeof nlist);
+	  if (!macho_defined_symbol (nlist.n_type))
+	    continue;
+
+	  strx = nlist.n_strx;
+	  value = nlist.n_value;
+	}
+
+      if (strx >= strsize)
+	{
+	  error_callback (data, "symbol string index out of range", 0);
+	  goto fail;
+	}
+
+      name = (const char *) str_view.data + strx;
+      if (name[0] == '_')
+	++name;
+      macho_symbols[j].name = name;
+      macho_symbols[j].address = value + base_address;
+      ++j;
+    }
+
+  sdata = ((struct macho_syminfo_data *)
+	   backtrace_alloc (state, sizeof *sdata, error_callback, data));
+  if (sdata == NULL)
+    goto fail;
+
+  /* We need to keep the string table since it holds the names, but we
+     can release the symbol table.  */
+
+  backtrace_release_view (state, &sym_view, error_callback, data);
+  sym_view_valid = 0;
+  str_view_valid = 0;
+
+  /* Add a trailing sentinel symbol.  */
+  macho_symbols[j].name = "";
+  macho_symbols[j].address = ~(uintptr_t) 0;
+
+  backtrace_qsort (macho_symbols, ndefs + 1, sizeof (struct macho_symbol),
+		   macho_symbol_compare);
+
+  sdata->next = NULL;
+  sdata->symbols = macho_symbols;
+  sdata->count = ndefs;
+
+  if (!state->threaded)
+    {
+      struct macho_syminfo_data **pp;
+
+      for (pp = (struct macho_syminfo_data **) (void *) &state->syminfo_data;
+	   *pp != NULL;
+	   pp = &(*pp)->next)
+	;
+      *pp = sdata;
+    }
+  else
+    {
+      while (1)
+	{
+	  struct macho_syminfo_data **pp;
+
+	  pp = (struct macho_syminfo_data **) (void *) &state->syminfo_data;
+
+	  while (1)
+	    {
+	      struct macho_syminfo_data *p;
+
+	      p = backtrace_atomic_load_pointer (pp);
+	      
+	      if (p == NULL)
+		break;
+
+	      pp = &p->next;
+	    }
+
+	  if (__sync_bool_compare_and_swap (pp, NULL, sdata))
+	    break;
+	}
+    }
+
+  return 1;
+
+ fail:
+  if (macho_symbols != NULL)
+    backtrace_free (state, macho_symbols, macho_symbol_size,
+		    error_callback, data);
+  if (sym_view_valid)
+    backtrace_release_view (state, &sym_view, error_callback, data);
+  if (str_view_valid)
+    backtrace_release_view (state, &str_view, error_callback, data);
+  return 0;
+}
+
+/* Return the symbol name and value for an ADDR.  */
+
+static void
+macho_syminfo (struct backtrace_state *state, uintptr_t addr,
+	       backtrace_syminfo_callback callback,
+	       backtrace_error_callback error_callback ATTRIBUTE_UNUSED,
+	       void *data)
+{
+  struct macho_syminfo_data *sdata;
+  struct macho_symbol *sym;
+
+  sym = NULL;
+  if (!state->threaded)
+    {
+      for (sdata = (struct macho_syminfo_data *) state->syminfo_data;
+	   sdata != NULL;
+	   sdata = sdata->next)
+	{
+	  sym = ((struct macho_symbol *)
+		 bsearch (&addr, sdata->symbols, sdata->count,
+			  sizeof (struct macho_symbol), macho_symbol_search));
+	  if (sym != NULL)
+	    break;
+	}
+    }
+  else
+    {
+      struct macho_syminfo_data **pp;
+
+      pp = (struct macho_syminfo_data **) (void *) &state->syminfo_data;
+      while (1)
+	{
+	  sdata = backtrace_atomic_load_pointer (pp);
+	  if (sdata == NULL)
+	    break;
+
+	  sym = ((struct macho_symbol *)
+		 bsearch (&addr, sdata->symbols, sdata->count,
+			  sizeof (struct macho_symbol), macho_symbol_search));
+	  if (sym != NULL)
+	    break;
+
+	  pp = &sdata->next;
+	}
+    }
+
+  if (sym == NULL)
+    callback (data, addr, NULL, 0, 0);
+  else
+    callback (data, addr, sym->name, sym->address, 0);
+}
+
+/* Look through a fat file to find the relevant executable.  Returns 1
+   on success, 0 on failure (in both cases descriptor is closed).  */
+
+static int
+macho_add_fat (struct backtrace_state *state, const char *filename,
+	       int descriptor, int swapped, off_t offset,
+	       const unsigned char *match_uuid, uintptr_t base_address,
+	       int skip_symtab, uint32_t nfat_arch, int is_64,
+	       backtrace_error_callback error_callback, void *data,
+	       fileline *fileline_fn, int *found_sym)
+{
+  int arch_view_valid;
+  unsigned int cputype;
+  size_t arch_size;
+  struct backtrace_view arch_view;
+  unsigned int i;
+
+  arch_view_valid = 0;
+
+#if defined (__x86_64__)
+  cputype = MACH_O_CPU_TYPE_X86_64;
+#elif defined (__i386__)
+  cputype = MACH_O_CPU_TYPE_X86;
+#elif defined (__aarch64__)
+  cputype = MACH_O_CPU_TYPE_ARM64;
+#elif defined (__arm__)
+  cputype = MACH_O_CPU_TYPE_ARM;
+#elif defined (__ppc__)
+  cputype = MACH_O_CPU_TYPE_PPC;
+#elif defined (__ppc64__)
+  cputype = MACH_O_CPU_TYPE_PPC64;
+#else
+  error_callback (data, "unknown Mach-O architecture", 0);
+  goto fail;
+#endif
+
+  if (is_64)
+    arch_size = sizeof (struct macho_fat_arch_64);
+  else
+    arch_size = sizeof (struct macho_fat_arch);
+
+  if (!backtrace_get_view (state, descriptor, offset,
+			   nfat_arch * arch_size,
+			   error_callback, data, &arch_view))
+    goto fail;
+
+  for (i = 0; i < nfat_arch; ++i)
+    {
+      uint32_t fcputype;
+      uint64_t foffset;
+
+      if (is_64)
+	{
+	  struct macho_fat_arch_64 fat_arch_64;
+
+	  memcpy (&fat_arch_64,
+		  (const char *) arch_view.data + i * arch_size,
+		  arch_size);
+	  fcputype = fat_arch_64.cputype;
+	  foffset = fat_arch_64.offset;
+	  if (swapped)
+	    {
+	      fcputype = __builtin_bswap32 (fcputype);
+	      foffset = __builtin_bswap64 (foffset);
+	    }
+	}
+      else
+	{
+	  struct macho_fat_arch fat_arch_32;
+
+	  memcpy (&fat_arch_32,
+		  (const char *) arch_view.data + i * arch_size,
+		  arch_size);
+	  fcputype = fat_arch_32.cputype;
+	  foffset = (uint64_t) fat_arch_32.offset;
+	  if (swapped)
+	    {
+	      fcputype = __builtin_bswap32 (fcputype);
+	      foffset = (uint64_t) __builtin_bswap32 ((uint32_t) foffset);
+	    }
+	}
+
+      if (fcputype == cputype)
+	{
+	  /* FIXME: What about cpusubtype?  */
+	  backtrace_release_view (state, &arch_view, error_callback, data);
+	  return macho_add (state, filename, descriptor, foffset, match_uuid,
+			    base_address, skip_symtab, error_callback, data,
+			    fileline_fn, found_sym);
+	}
+    }
+
+  error_callback (data, "could not find executable in fat file", 0);
+
+ fail:
+  if (arch_view_valid)
+    backtrace_release_view (state, &arch_view, error_callback, data);
+  if (descriptor != -1)
+    backtrace_close (descriptor, error_callback, data);
+  return 0;
+}
+
+/* Look for the dsym file for FILENAME.  This is called if FILENAME
+   does not have debug info or a symbol table.  Returns 1 on success,
+   0 on failure.  */
+
+static int
+macho_add_dsym (struct backtrace_state *state, const char *filename,
+		uintptr_t base_address, const unsigned char *uuid,
+		backtrace_error_callback error_callback, void *data,
+		fileline* fileline_fn)
+{
+  const char *p;
+  const char *dirname;
+  char *diralc;
+  size_t dirnamelen;
+  const char *basename;
+  size_t basenamelen;
+  const char *dsymsuffixdir;
+  size_t dsymsuffixdirlen;
+  size_t dsymlen;
+  char *dsym;
+  char *ps;
+  int d;
+  int does_not_exist;
+  int dummy_found_sym;
+
+  diralc = NULL;
+  dirnamelen = 0;
+  dsym = NULL;
+  dsymlen = 0;
+
+  p = strrchr (filename, '/');
+  if (p == NULL)
+    {
+      dirname = ".";
+      dirnamelen = 1;
+      basename = filename;
+      basenamelen = strlen (basename);
+      diralc = NULL;
+    }
+  else
+    {
+      dirnamelen = p - filename;
+      diralc = backtrace_alloc (state, dirnamelen + 1, error_callback, data);
+      if (diralc == NULL)
+	goto fail;
+      memcpy (diralc, filename, dirnamelen);
+      diralc[dirnamelen] = '\0';
+      dirname = diralc;
+      basename = p + 1;
+      basenamelen = strlen (basename);
+    }
+
+  dsymsuffixdir = ".dSYM/Contents/Resources/DWARF/";
+  dsymsuffixdirlen = strlen (dsymsuffixdir);
+
+  dsymlen = (dirnamelen
+	     + 1
+	     + basenamelen
+	     + dsymsuffixdirlen
+	     + basenamelen
+	     + 1);
+  dsym = backtrace_alloc (state, dsymlen, error_callback, data);
+  if (dsym == NULL)
+    goto fail;
+
+  ps = dsym;
+  memcpy (ps, dirname, dirnamelen);
+  ps += dirnamelen;
+  *ps++ = '/';
+  memcpy (ps, basename, basenamelen);
+  ps += basenamelen;
+  memcpy (ps, dsymsuffixdir, dsymsuffixdirlen);
+  ps += dsymsuffixdirlen;
+  memcpy (ps, basename, basenamelen);
+  ps += basenamelen;
+  *ps = '\0';
+
+  if (diralc != NULL)
+    {
+      backtrace_free (state, diralc, dirnamelen + 1, error_callback, data);
+      diralc = NULL;
+    }
+
+  d = backtrace_open (dsym, error_callback, data, &does_not_exist);
+  if (d < 0)
+    {
+      /* The file does not exist, so we can't read the debug info.
+	 Just return success.  */
+      backtrace_free (state, dsym, dsymlen, error_callback, data);
+      return 1;
+    }
+
+  if (!macho_add (state, dsym, d, 0, uuid, base_address, 1,
+		  error_callback, data, fileline_fn, &dummy_found_sym))
+    goto fail;
+
+  backtrace_free (state, dsym, dsymlen, error_callback, data);
+
+  return 1;
+
+ fail:
+  if (dsym != NULL)
+    backtrace_free (state, dsym, dsymlen, error_callback, data);
+  if (diralc != NULL)
+    backtrace_free (state, diralc, dirnamelen, error_callback, data);
+  return 0;
+}
+
+/* Add the backtrace data for a Macho-O file.  Returns 1 on success, 0
+   on failure (in both cases descriptor is closed).
+
+   FILENAME: the name of the executable.
+   DESCRIPTOR: an open descriptor for the executable, closed here.
+   OFFSET: the offset within the file of this executable, for fat files.
+   MATCH_UUID: if not NULL, UUID that must match.
+   BASE_ADDRESS: the load address of the executable.
+   SKIP_SYMTAB: if non-zero, ignore the symbol table; used for dSYM files.
+   FILELINE_FN: set to the fileline function, by backtrace_dwarf_add.
+   FOUND_SYM: set to non-zero if we found the symbol table.
+*/
+
+static int
+macho_add (struct backtrace_state *state, const char *filename, int descriptor,
+	   off_t offset, const unsigned char *match_uuid,
+	   uintptr_t base_address, int skip_symtab,
+	   backtrace_error_callback error_callback, void *data,
+	   fileline *fileline_fn, int *found_sym)
+{
+  struct backtrace_view header_view;
+  struct macho_header_32 header;
+  off_t hdroffset;
+  int is_64;
+  struct backtrace_view cmds_view;
+  int cmds_view_valid;
+  struct dwarf_sections dwarf_sections;
+  int have_dwarf;
+  unsigned char uuid[MACH_O_UUID_LEN];
+  int have_uuid;
+  size_t cmdoffset;
+  unsigned int i;
+
+  *found_sym = 0;
+
+  cmds_view_valid = 0;
+
+  /* The 32-bit and 64-bit file headers start out the same, so we can
+     just always read the 32-bit version.  A fat header is shorter but
+     it will always be followed by data, so it's OK to read extra.  */
+
+  if (!backtrace_get_view (state, descriptor, offset,
+			   sizeof (struct macho_header_32),
+			   error_callback, data, &header_view))
+    goto fail;
+
+  memcpy (&header, header_view.data, sizeof header);
+
+  backtrace_release_view (state, &header_view, error_callback, data);
+
+  switch (header.magic)
+    {
+    case MACH_O_MH_MAGIC_32:
+      is_64 = 0;
+      hdroffset = offset + sizeof (struct macho_header_32);
+      break;
+    case MACH_O_MH_MAGIC_64:
+      is_64 = 1;
+      hdroffset = offset + sizeof (struct macho_header_64);
+      break;
+    case MACH_O_MH_MAGIC_FAT:
+    case MACH_O_MH_MAGIC_FAT_64:
+      {
+	struct macho_header_fat fat_header;
+
+	hdroffset = offset + sizeof (struct macho_header_fat);
+	memcpy (&fat_header, &header, sizeof fat_header);
+	return macho_add_fat (state, filename, descriptor, 0, hdroffset,
+			      match_uuid, base_address, skip_symtab,
+			      fat_header.nfat_arch,
+			      header.magic == MACH_O_MH_MAGIC_FAT_64,
+			      error_callback, data, fileline_fn, found_sym);
+      }
+    case MACH_O_MH_CIGAM_FAT:
+    case MACH_O_MH_CIGAM_FAT_64:
+      {
+	struct macho_header_fat fat_header;
+	uint32_t nfat_arch;
+
+	hdroffset = offset + sizeof (struct macho_header_fat);
+	memcpy (&fat_header, &header, sizeof fat_header);
+	nfat_arch = __builtin_bswap32 (fat_header.nfat_arch);
+	return macho_add_fat (state, filename, descriptor, 1, hdroffset,
+			      match_uuid, base_address, skip_symtab,
+			      nfat_arch,
+			      header.magic == MACH_O_MH_CIGAM_FAT_64,
+			      error_callback, data, fileline_fn, found_sym);
+      }
+    default:
+      error_callback (data, "executable file is not in Mach-O format", 0);
+      goto fail;
+    }
+
+  switch (header.filetype)
+    {
+    case MACH_O_MH_EXECUTE:
+    case MACH_O_MH_DYLIB:
+    case MACH_O_MH_DSYM:
+      break;
+    default:
+      error_callback (data, "executable file is not an executable", 0);
+      goto fail;
+    }
+
+  if (!backtrace_get_view (state, descriptor, hdroffset, header.sizeofcmds,
+			   error_callback, data, &cmds_view))
+    goto fail;
+  cmds_view_valid = 1;
+
+  memset (&dwarf_sections, 0, sizeof dwarf_sections);
+  have_dwarf = 0;
+  memset (&uuid, 0, sizeof uuid);
+  have_uuid = 0;
+
+  cmdoffset = 0;
+  for (i = 0; i < header.ncmds; ++i)
+    {
+      const char *pcmd;
+      struct macho_load_command load_command;
+
+      if (cmdoffset + sizeof load_command > header.sizeofcmds)
+	break;
+
+      pcmd = (const char *) cmds_view.data + cmdoffset;
+      memcpy (&load_command, pcmd, sizeof load_command);
+
+      switch (load_command.cmd)
+	{
+	case MACH_O_LC_SEGMENT:
+	  {
+	    struct macho_segment_command segcmd;
+
+	    memcpy (&segcmd, pcmd, sizeof segcmd);
+	    if (memcmp (segcmd.segname,
+			"__DWARF\0\0\0\0\0\0\0\0\0",
+			MACH_O_NAMELEN) == 0)
+	      {
+		if (!macho_add_dwarf_segment (state, descriptor, offset,
+					      load_command.cmd,
+					      pcmd + sizeof segcmd,
+					      (load_command.cmdsize
+					       - sizeof segcmd),
+					      segcmd.nsects, error_callback,
+					      data, &dwarf_sections))
+		  goto fail;
+		have_dwarf = 1;
+	      }
+	  }
+	  break;
+
+	case MACH_O_LC_SEGMENT_64:
+	  {
+	    struct macho_segment_64_command segcmd;
+
+	    memcpy (&segcmd, pcmd, sizeof segcmd);
+	    if (memcmp (segcmd.segname,
+			"__DWARF\0\0\0\0\0\0\0\0\0",
+			MACH_O_NAMELEN) == 0)
+	      {
+		if (!macho_add_dwarf_segment (state, descriptor, offset,
+					      load_command.cmd,
+					      pcmd + sizeof segcmd,
+					      (load_command.cmdsize
+					       - sizeof segcmd),
+					      segcmd.nsects, error_callback,
+					      data, &dwarf_sections))
+		  goto fail;
+		have_dwarf = 1;
+	      }
+	  }
+	  break;
+
+	case MACH_O_LC_SYMTAB:
+	  if (!skip_symtab)
+	    {
+	      struct macho_symtab_command symcmd;
+
+	      memcpy (&symcmd, pcmd, sizeof symcmd);
+	      if (!macho_add_symtab (state, descriptor, base_address, is_64,
+				     offset + symcmd.symoff, symcmd.nsyms,
+				     offset + symcmd.stroff, symcmd.strsize,
+				     error_callback, data))
+		goto fail;
+
+	      *found_sym = 1;
+	    }
+	  break;
+
+	case MACH_O_LC_UUID:
+	  {
+	    struct macho_uuid_command uuidcmd;
+
+	    memcpy (&uuidcmd, pcmd, sizeof uuidcmd);
+	    memcpy (&uuid[0], &uuidcmd.uuid[0], MACH_O_UUID_LEN);
+	    have_uuid = 1;
+	  }
+	  break;
+
+	default:
+	  break;
+	}
+
+      cmdoffset += load_command.cmdsize;
+    }
+
+  if (!backtrace_close (descriptor, error_callback, data))
+    goto fail;
+  descriptor = -1;
+
+  backtrace_release_view (state, &cmds_view, error_callback, data);
+  cmds_view_valid = 0;
+
+  if (match_uuid != NULL)
+    {
+      /* If we don't have a UUID, or it doesn't match, just ignore
+	 this file.  */
+      if (!have_uuid
+	  || memcmp (match_uuid, &uuid[0], MACH_O_UUID_LEN) != 0)
+	return 1;
+    }
+
+  if (have_dwarf)
+    {
+      int is_big_endian;
+
+      is_big_endian = 0;
+#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__)
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+      is_big_endian = 1;
+#endif
+#endif
+
+      if (!backtrace_dwarf_add (state, base_address, &dwarf_sections,
+				is_big_endian, NULL, error_callback, data,
+				fileline_fn, NULL))
+	goto fail;
+    }
+
+  if (!have_dwarf && have_uuid)
+    {
+      if (!macho_add_dsym (state, filename, base_address, &uuid[0],
+			   error_callback, data, fileline_fn))
+	goto fail;
+    }
+
+  return 1;
+
+ fail:
+  if (cmds_view_valid)
+    backtrace_release_view (state, &cmds_view, error_callback, data);
+  if (descriptor != -1)
+    backtrace_close (descriptor, error_callback, data);
+  return 0;
+}
+
+#ifdef HAVE_MACH_O_DYLD_H
+
+/* Initialize the backtrace data we need from a Mach-O executable
+   using the dyld support functions.  This closes descriptor.  */
+
+int
+backtrace_initialize (struct backtrace_state *state, const char *filename,
+		      int descriptor, backtrace_error_callback error_callback,
+		      void *data, fileline *fileline_fn)
+{
+  uint32_t c;
+  uint32_t i;
+  int closed_descriptor;
+  int found_sym;
+  fileline macho_fileline_fn;
+
+  closed_descriptor = 0;
+  found_sym = 0;
+  macho_fileline_fn = macho_nodebug;
+
+  c = _dyld_image_count ();
+  for (i = 0; i < c; ++i)
+    {
+      uintptr_t base_address;
+      const char *name;
+      int d;
+      fileline mff;
+      int mfs;
+
+      name = _dyld_get_image_name (i);
+      if (name == NULL)
+	continue;
+
+      if (strcmp (name, filename) == 0 && !closed_descriptor)
+	{
+	  d = descriptor;
+	  closed_descriptor = 1;
+	}
+      else
+	{
+	  int does_not_exist;
+
+	  d = backtrace_open (name, error_callback, data, &does_not_exist);
+	  if (d < 0)
+	    continue;
+	}
+
+      base_address = _dyld_get_image_vmaddr_slide (i);
+
+      mff = macho_nodebug;
+      if (!macho_add (state, name, d, 0, NULL, base_address, 0,
+		      error_callback, data, &mff, &mfs))
+	continue;
+
+      if (mff != macho_nodebug)
+	macho_fileline_fn = mff;
+      if (mfs)
+	found_sym = 1;
+    }
+
+  if (!closed_descriptor)
+    backtrace_close (descriptor, error_callback, data);
+
+  if (!state->threaded)
+    {
+      if (found_sym)
+	state->syminfo_fn = macho_syminfo;
+      else if (state->syminfo_fn == NULL)
+	state->syminfo_fn = macho_nosyms;
+    }
+  else
+    {
+      if (found_sym)
+	backtrace_atomic_store_pointer (&state->syminfo_fn, macho_syminfo);
+      else
+	(void) __sync_bool_compare_and_swap (&state->syminfo_fn, NULL,
+					     macho_nosyms);
+    }
+
+  if (!state->threaded)
+    *fileline_fn = state->fileline_fn;
+  else
+    *fileline_fn = backtrace_atomic_load_pointer (&state->fileline_fn);
+
+  if (*fileline_fn == NULL || *fileline_fn == macho_nodebug)
+    *fileline_fn = macho_fileline_fn;
+
+  return 1;
+}
+
+#else /* !defined (HAVE_MACH_O_DYLD_H) */
+
+/* Initialize the backtrace data we need from a Mach-O executable
+   without using the dyld support functions.  This closes
+   descriptor.  */
+
+int
+backtrace_initialize (struct backtrace_state *state, const char *filename,
+		      int descriptor, backtrace_error_callback error_callback,
+		      void *data, fileline *fileline_fn)
+{
+  fileline macho_fileline_fn;
+  int found_sym;
+
+  macho_fileline_fn = macho_nodebug;
+  if (!macho_add (state, filename, descriptor, 0, NULL, 0, 0,
+		  error_callback, data, &macho_fileline_fn, &found_sym))
+    return 0;
+
+  if (!state->threaded)
+    {
+      if (found_sym)
+	state->syminfo_fn = macho_syminfo;
+      else if (state->syminfo_fn == NULL)
+	state->syminfo_fn = macho_nosyms;
+    }
+  else
+    {
+      if (found_sym)
+	backtrace_atomic_store_pointer (&state->syminfo_fn, macho_syminfo);
+      else
+	(void) __sync_bool_compare_and_swap (&state->syminfo_fn, NULL,
+					     macho_nosyms);
+    }
+
+  if (!state->threaded)
+    *fileline_fn = state->fileline_fn;
+  else
+    *fileline_fn = backtrace_atomic_load_pointer (&state->fileline_fn);
+
+  if (*fileline_fn == NULL || *fileline_fn == macho_nodebug)
+    *fileline_fn = macho_fileline_fn;
+
+  return 1;
+}
+
+#endif /* !defined (HAVE_MACH_O_DYLD_H) */

+ 425 - 0
contrib/libs/cxxsupp/libcxx/include/experimental/functional

@@ -0,0 +1,425 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP_EXPERIMENTAL_FUNCTIONAL
+#define _LIBCPP_EXPERIMENTAL_FUNCTIONAL
+
+/*
+   experimental/functional synopsis
+
+#include <algorithm>
+
+namespace std {
+namespace experimental {
+inline namespace fundamentals_v1 {
+    // 4.3, Searchers
+    template<class ForwardIterator, class BinaryPredicate = equal_to<>>
+      class default_searcher;
+
+    template<class RandomAccessIterator,
+             class Hash = hash<typename iterator_traits<RandomAccessIterator>::value_type>,
+             class BinaryPredicate = equal_to<>>
+      class boyer_moore_searcher;
+
+    template<class RandomAccessIterator,
+             class Hash = hash<typename iterator_traits<RandomAccessIterator>::value_type>,
+             class BinaryPredicate = equal_to<>>
+      class boyer_moore_horspool_searcher;
+
+    template<class ForwardIterator, class BinaryPredicate = equal_to<>>
+    default_searcher<ForwardIterator, BinaryPredicate>
+    make_default_searcher(ForwardIterator pat_first, ForwardIterator pat_last,
+                          BinaryPredicate pred = BinaryPredicate());
+
+    template<class RandomAccessIterator,
+             class Hash = hash<typename iterator_traits<RandomAccessIterator>::value_type>,
+             class BinaryPredicate = equal_to<>>
+    boyer_moore_searcher<RandomAccessIterator, Hash, BinaryPredicate>
+    make_boyer_moore_searcher(
+        RandomAccessIterator pat_first, RandomAccessIterator pat_last,
+        Hash hf = Hash(), BinaryPredicate pred = BinaryPredicate());
+
+    template<class RandomAccessIterator,
+             class Hash = hash<typename iterator_traits<RandomAccessIterator>::value_type>,
+             class BinaryPredicate = equal_to<>>
+    boyer_moore_horspool_searcher<RandomAccessIterator, Hash, BinaryPredicate>
+    make_boyer_moore_horspool_searcher(
+        RandomAccessIterator pat_first, RandomAccessIterator pat_last,
+        Hash hf = Hash(), BinaryPredicate pred = BinaryPredicate());
+
+  } // namespace fundamentals_v1
+  } // namespace experimental
+
+} // namespace std
+
+*/
+
+#include <__debug>
+#include <__memory/uses_allocator.h>
+#include <array>
+#include <experimental/__config>
+#include <functional>
+#include <type_traits>
+#include <unordered_map>
+#include <vector>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+_LIBCPP_BEGIN_NAMESPACE_LFTS
+
+#if _LIBCPP_STD_VER > 11
+// default searcher
+template<class _ForwardIterator, class _BinaryPredicate = equal_to<>>
+class _LIBCPP_TEMPLATE_VIS default_searcher {
+public:
+    _LIBCPP_INLINE_VISIBILITY
+    default_searcher(_ForwardIterator __f, _ForwardIterator __l,
+                       _BinaryPredicate __p = _BinaryPredicate())
+        : __first_(__f), __last_(__l), __pred_(__p) {}
+
+    template <typename _ForwardIterator2>
+    _LIBCPP_INLINE_VISIBILITY
+    pair<_ForwardIterator2, _ForwardIterator2>
+    operator () (_ForwardIterator2 __f, _ForwardIterator2 __l) const
+    {
+        return _VSTD::__search(__f, __l, __first_, __last_, __pred_,
+            typename iterator_traits<_ForwardIterator>::iterator_category(),
+            typename iterator_traits<_ForwardIterator2>::iterator_category());
+    }
+
+private:
+    _ForwardIterator __first_;
+    _ForwardIterator __last_;
+    _BinaryPredicate __pred_;
+    };
+
+template<class _ForwardIterator, class _BinaryPredicate = equal_to<>>
+_LIBCPP_INLINE_VISIBILITY
+default_searcher<_ForwardIterator, _BinaryPredicate>
+make_default_searcher( _ForwardIterator __f, _ForwardIterator __l, _BinaryPredicate __p = _BinaryPredicate ())
+{
+    return default_searcher<_ForwardIterator, _BinaryPredicate>(__f, __l, __p);
+}
+
+template<class _Key, class _Value, class _Hash, class _BinaryPredicate, bool /*useArray*/> class _BMSkipTable;
+
+//  General case for BM data searching; use a map
+template<class _Key, typename _Value, class _Hash, class _BinaryPredicate>
+class _BMSkipTable<_Key, _Value, _Hash, _BinaryPredicate, false> {
+    typedef _Value value_type;
+    typedef _Key   key_type;
+
+    const _Value __default_value_;
+    std::unordered_map<_Key, _Value, _Hash, _BinaryPredicate> __table;
+
+public:
+    _LIBCPP_INLINE_VISIBILITY
+    _BMSkipTable(size_t __sz, _Value __default, _Hash __hf, _BinaryPredicate __pred)
+        : __default_value_(__default), __table(__sz, __hf, __pred) {}
+
+    _LIBCPP_INLINE_VISIBILITY
+    void insert(const key_type &__key, value_type __val)
+    {
+        __table [__key] = __val;    // Would skip_.insert (val) be better here?
+    }
+
+    _LIBCPP_INLINE_VISIBILITY
+    value_type operator [](const key_type & __key) const
+    {
+        auto __it = __table.find (__key);
+        return __it == __table.end() ? __default_value_ : __it->second;
+    }
+};
+
+
+//  Special case small numeric values; use an array
+template<class _Key, typename _Value, class _Hash, class _BinaryPredicate>
+class _BMSkipTable<_Key, _Value, _Hash, _BinaryPredicate, true> {
+private:
+    typedef _Value value_type;
+    typedef _Key   key_type;
+
+    typedef typename make_unsigned<key_type>::type unsigned_key_type;
+    typedef std::array<value_type, numeric_limits<unsigned_key_type>::max()> skip_map;
+    skip_map __table;
+
+public:
+    _LIBCPP_INLINE_VISIBILITY
+    _BMSkipTable(size_t /*__sz*/, _Value __default, _Hash /*__hf*/, _BinaryPredicate /*__pred*/)
+    {
+        std::fill_n(__table.begin(), __table.size(), __default);
+    }
+
+    _LIBCPP_INLINE_VISIBILITY
+    void insert(key_type __key, value_type __val)
+    {
+        __table[static_cast<unsigned_key_type>(__key)] = __val;
+    }
+
+    _LIBCPP_INLINE_VISIBILITY
+    value_type operator [](key_type __key) const
+    {
+        return __table[static_cast<unsigned_key_type>(__key)];
+    }
+};
+
+
+template <class _RandomAccessIterator1,
+          class _Hash = hash<typename iterator_traits<_RandomAccessIterator1>::value_type>,
+          class _BinaryPredicate = equal_to<>>
+class _LIBCPP_TEMPLATE_VIS boyer_moore_searcher {
+private:
+    typedef typename std::iterator_traits<_RandomAccessIterator1>::difference_type difference_type;
+    typedef typename std::iterator_traits<_RandomAccessIterator1>::value_type      value_type;
+    typedef _BMSkipTable<value_type, difference_type, _Hash, _BinaryPredicate,
+                    is_integral<value_type>::value && // what about enums?
+                    sizeof(value_type) == 1 &&
+                    is_same<_Hash, hash<value_type>>::value &&
+                    is_same<_BinaryPredicate, equal_to<>>::value
+            > skip_table_type;
+
+public:
+    boyer_moore_searcher(_RandomAccessIterator1 __f, _RandomAccessIterator1 __l,
+                _Hash __hf = _Hash(), _BinaryPredicate __pred = _BinaryPredicate())
+            : __first_(__f), __last_(__l), __pred_(__pred),
+              __pattern_length_(_VSTD::distance(__first_, __last_)),
+              __skip_{make_shared<skip_table_type>(__pattern_length_, -1, __hf, __pred_)},
+              __suffix_{make_shared<vector<difference_type>>(__pattern_length_ + 1)}
+        {
+    //  build the skip table
+        for ( difference_type __i = 0; __f != __l; ++__f, (void) ++__i )
+            __skip_->insert(*__f, __i);
+
+        this->__build_suffix_table ( __first_, __last_, __pred_ );
+        }
+
+    template <typename _RandomAccessIterator2>
+    pair<_RandomAccessIterator2, _RandomAccessIterator2>
+    operator ()(_RandomAccessIterator2 __f, _RandomAccessIterator2 __l) const
+    {
+        static_assert(__is_same_uncvref<typename iterator_traits<_RandomAccessIterator1>::value_type,
+                                        typename iterator_traits<_RandomAccessIterator2>::value_type>::value,
+                      "Corpus and Pattern iterators must point to the same type");
+
+        if (__f      == __l )    return make_pair(__l, __l); // empty corpus
+        if (__first_ == __last_) return make_pair(__f, __f); // empty pattern
+
+    //  If the pattern is larger than the corpus, we can't find it!
+        if ( __pattern_length_ > _VSTD::distance(__f, __l))
+            return make_pair(__l, __l);
+
+    //  Do the search
+        return this->__search(__f, __l);
+    }
+
+private:
+    _RandomAccessIterator1               __first_;
+    _RandomAccessIterator1               __last_;
+    _BinaryPredicate                     __pred_;
+    difference_type                      __pattern_length_;
+    shared_ptr<skip_table_type>          __skip_;
+    shared_ptr<vector<difference_type>>  __suffix_;
+
+    template <typename _RandomAccessIterator2>
+    pair<_RandomAccessIterator2, _RandomAccessIterator2>
+    __search(_RandomAccessIterator2 __f, _RandomAccessIterator2 __l) const
+    {
+        _RandomAccessIterator2 __cur = __f;
+        const _RandomAccessIterator2 __last = __l - __pattern_length_;
+        const skip_table_type &         __skip   = *__skip_.get();
+        const vector<difference_type> & __suffix = *__suffix_.get();
+
+        while (__cur <= __last)
+        {
+
+        //  Do we match right where we are?
+            difference_type __j = __pattern_length_;
+            while (__pred_(__first_ [__j-1], __cur [__j-1])) {
+                __j--;
+            //  We matched - we're done!
+                if ( __j == 0 )
+                    return make_pair(__cur, __cur + __pattern_length_);
+                }
+
+        //  Since we didn't match, figure out how far to skip forward
+            difference_type __k = __skip[__cur [ __j - 1 ]];
+            difference_type __m = __j - __k - 1;
+            if (__k < __j && __m > __suffix[ __j ])
+                __cur += __m;
+            else
+                __cur += __suffix[ __j ];
+        }
+
+        return make_pair(__l, __l);     // We didn't find anything
+    }
+
+
+    template<typename _Iterator, typename _Container>
+    void __compute_bm_prefix ( _Iterator __f, _Iterator __l, _BinaryPredicate __pred, _Container &__prefix )
+    {
+        const size_t __count = _VSTD::distance(__f, __l);
+
+        __prefix[0] = 0;
+        size_t __k = 0;
+        for ( size_t __i = 1; __i < __count; ++__i )
+        {
+            while ( __k > 0 && !__pred ( __f[__k], __f[__i] ))
+                __k = __prefix [ __k - 1 ];
+
+            if ( __pred ( __f[__k], __f[__i] ))
+                __k++;
+            __prefix [ __i ] = __k;
+        }
+    }
+
+    void __build_suffix_table(_RandomAccessIterator1 __f, _RandomAccessIterator1 __l,
+                                                    _BinaryPredicate __pred)
+    {
+        const size_t __count = _VSTD::distance(__f, __l);
+        vector<difference_type> & __suffix = *__suffix_.get();
+        if (__count > 0)
+        {
+            vector<value_type> __scratch(__count);
+
+            __compute_bm_prefix(__f, __l, __pred, __scratch);
+            for ( size_t __i = 0; __i <= __count; __i++ )
+                __suffix[__i] = __count - __scratch[__count-1];
+
+            typedef reverse_iterator<_RandomAccessIterator1> _RevIter;
+            __compute_bm_prefix(_RevIter(__l), _RevIter(__f), __pred, __scratch);
+
+            for ( size_t __i = 0; __i < __count; __i++ )
+            {
+                const size_t     __j = __count - __scratch[__i];
+                const difference_type __k = __i     - __scratch[__i] + 1;
+
+                if (__suffix[__j] > __k)
+                    __suffix[__j] = __k;
+            }
+        }
+    }
+
+};
+
+template<class _RandomAccessIterator,
+         class _Hash = hash<typename iterator_traits<_RandomAccessIterator>::value_type>,
+         class _BinaryPredicate = equal_to<>>
+_LIBCPP_INLINE_VISIBILITY
+boyer_moore_searcher<_RandomAccessIterator, _Hash, _BinaryPredicate>
+make_boyer_moore_searcher( _RandomAccessIterator __f, _RandomAccessIterator __l,
+                    _Hash __hf = _Hash(), _BinaryPredicate __p = _BinaryPredicate ())
+{
+    return boyer_moore_searcher<_RandomAccessIterator, _Hash, _BinaryPredicate>(__f, __l, __hf, __p);
+}
+
+// boyer-moore-horspool
+template <class _RandomAccessIterator1,
+          class _Hash = hash<typename iterator_traits<_RandomAccessIterator1>::value_type>,
+          class _BinaryPredicate = equal_to<>>
+class _LIBCPP_TEMPLATE_VIS boyer_moore_horspool_searcher {
+private:
+    typedef typename std::iterator_traits<_RandomAccessIterator1>::difference_type difference_type;
+    typedef typename std::iterator_traits<_RandomAccessIterator1>::value_type      value_type;
+    typedef _BMSkipTable<value_type, difference_type, _Hash, _BinaryPredicate,
+                    is_integral<value_type>::value && // what about enums?
+                    sizeof(value_type) == 1 &&
+                    is_same<_Hash, hash<value_type>>::value &&
+                    is_same<_BinaryPredicate, equal_to<>>::value
+            > skip_table_type;
+
+public:
+    boyer_moore_horspool_searcher(_RandomAccessIterator1 __f, _RandomAccessIterator1 __l,
+                _Hash __hf = _Hash(), _BinaryPredicate __pred = _BinaryPredicate())
+            : __first_(__f), __last_(__l), __pred_(__pred),
+              __pattern_length_(_VSTD::distance(__first_, __last_)),
+              __skip_{_VSTD::make_shared<skip_table_type>(__pattern_length_, __pattern_length_, __hf, __pred_)}
+        {
+    //  build the skip table
+            if ( __f != __l )
+            {
+                __l = __l - 1;
+                for ( difference_type __i = 0; __f != __l; ++__f, (void) ++__i )
+                    __skip_->insert(*__f, __pattern_length_ - 1 - __i);
+            }
+        }
+
+    template <typename _RandomAccessIterator2>
+    pair<_RandomAccessIterator2, _RandomAccessIterator2>
+    operator ()(_RandomAccessIterator2 __f, _RandomAccessIterator2 __l) const
+    {
+        static_assert(__is_same_uncvref<typename std::iterator_traits<_RandomAccessIterator1>::value_type,
+                                        typename std::iterator_traits<_RandomAccessIterator2>::value_type>::value,
+                      "Corpus and Pattern iterators must point to the same type");
+
+        if (__f      == __l )    return make_pair(__l, __l); // empty corpus
+        if (__first_ == __last_) return make_pair(__f, __f); // empty pattern
+
+    //  If the pattern is larger than the corpus, we can't find it!
+        if ( __pattern_length_ > _VSTD::distance(__f, __l))
+            return make_pair(__l, __l);
+
+    //  Do the search
+        return this->__search(__f, __l);
+    }
+
+private:
+    _RandomAccessIterator1      __first_;
+    _RandomAccessIterator1      __last_;
+    _BinaryPredicate            __pred_;
+    difference_type             __pattern_length_;
+    shared_ptr<skip_table_type> __skip_;
+
+    template <typename _RandomAccessIterator2>
+    pair<_RandomAccessIterator2, _RandomAccessIterator2>
+    __search ( _RandomAccessIterator2 __f, _RandomAccessIterator2 __l ) const {
+        _RandomAccessIterator2 __cur = __f;
+        const _RandomAccessIterator2 __last = __l - __pattern_length_;
+        const skip_table_type & __skip = *__skip_.get();
+
+        while (__cur <= __last)
+        {
+        //  Do we match right where we are?
+            difference_type __j = __pattern_length_;
+            while (__pred_(__first_[__j-1], __cur[__j-1]))
+            {
+                __j--;
+            //  We matched - we're done!
+                if ( __j == 0 )
+                    return make_pair(__cur, __cur + __pattern_length_);
+            }
+            __cur += __skip[__cur[__pattern_length_-1]];
+        }
+
+        return make_pair(__l, __l);
+    }
+};
+
+template<class _RandomAccessIterator,
+         class _Hash = hash<typename iterator_traits<_RandomAccessIterator>::value_type>,
+         class _BinaryPredicate = equal_to<>>
+_LIBCPP_INLINE_VISIBILITY
+boyer_moore_horspool_searcher<_RandomAccessIterator, _Hash, _BinaryPredicate>
+make_boyer_moore_horspool_searcher( _RandomAccessIterator __f, _RandomAccessIterator __l,
+                    _Hash __hf = _Hash(), _BinaryPredicate __p = _BinaryPredicate ())
+{
+    return boyer_moore_horspool_searcher<_RandomAccessIterator, _Hash, _BinaryPredicate>(__f, __l, __hf, __p);
+}
+
+#endif // _LIBCPP_STD_VER > 11
+
+_LIBCPP_END_NAMESPACE_LFTS
+
+_LIBCPP_POP_MACROS
+
+#endif /* _LIBCPP_EXPERIMENTAL_FUNCTIONAL */

+ 338 - 0
contrib/libs/sparsehash/src/sparsehash/dense_hash_set

@@ -0,0 +1,338 @@
+// Copyright (c) 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// ---
+//
+// This is just a very thin wrapper over densehashtable.h, just
+// like sgi stl's stl_hash_set is a very thin wrapper over
+// stl_hashtable.  The major thing we define is operator[], because
+// we have a concept of a data_type which stl_hashtable doesn't
+// (it only has a key and a value).
+//
+// This is more different from dense_hash_map than you might think,
+// because all iterators for sets are const (you obviously can't
+// change the key, and for sets there is no value).
+//
+// NOTE: this is exactly like sparse_hash_set.h, with the word
+// "sparse" replaced by "dense", except for the addition of
+// set_empty_key().
+//
+//   YOU MUST CALL SET_EMPTY_KEY() IMMEDIATELY AFTER CONSTRUCTION.
+//
+// Otherwise your program will die in mysterious ways.  (Note if you
+// use the constructor that takes an InputIterator range, you pass in
+// the empty key in the constructor, rather than after.  As a result,
+// this constructor differs from the standard STL version.)
+//
+// In other respects, we adhere mostly to the STL semantics for
+// hash-map.  One important exception is that insert() may invalidate
+// iterators entirely -- STL semantics are that insert() may reorder
+// iterators, but they all still refer to something valid in the
+// hashtable.  Not so for us.  Likewise, insert() may invalidate
+// pointers into the hashtable.  (Whether insert invalidates iterators
+// and pointers depends on whether it results in a hashtable resize).
+// On the plus side, delete() doesn't invalidate iterators or pointers
+// at all, or even change the ordering of elements.
+//
+// Here are a few "power user" tips:
+//
+//    1) set_deleted_key():
+//         If you want to use erase() you must call set_deleted_key(),
+//         in addition to set_empty_key(), after construction.
+//         The deleted and empty keys must differ.
+//
+//    2) resize(0):
+//         When an item is deleted, its memory isn't freed right
+//         away.  This allows you to iterate over a hashtable,
+//         and call erase(), without invalidating the iterator.
+//         To force the memory to be freed, call resize(0).
+//         For tr1 compatibility, this can also be called as rehash(0).
+//
+//    3) min_load_factor(0.0)
+//         Setting the minimum load factor to 0.0 guarantees that
+//         the hash table will never shrink.
+//
+// Roughly speaking:
+//   (1) dense_hash_set: fastest, uses the most memory unless entries are small
+//   (2) sparse_hash_set: slowest, uses the least memory
+//   (3) hash_set / unordered_set (STL): in the middle
+//
+// Typically I use sparse_hash_set when I care about space and/or when
+// I need to save the hashtable on disk.  I use hash_set otherwise.  I
+// don't personally use dense_hash_set ever; some people use it for
+// small sets with lots of lookups.
+//
+// - dense_hash_set has, typically, about 78% memory overhead (if your
+//   data takes up X bytes, the hash_set uses .78X more bytes in overhead).
+// - sparse_hash_set has about 4 bits overhead per entry.
+// - sparse_hash_set can be 3-7 times slower than the others for lookup and,
+//   especially, inserts.  See time_hash_map.cc for details.
+//
+// See /usr/(local/)?doc/sparsehash-*/dense_hash_set.html
+// for information about how to use this class.
+
+#ifndef _DENSE_HASH_SET_H_
+#define _DENSE_HASH_SET_H_
+
+#include <sparsehash/internal/sparseconfig.h>
+#include <algorithm>                        // needed by stl_alloc
+#include <functional>                       // for equal_to<>, select1st<>, etc
+#include <memory>                           // for alloc
+#include <utility>                          // for pair<>
+#include <sparsehash/internal/densehashtable.h>        // IWYU pragma: export
+#include <sparsehash/internal/libc_allocator_with_realloc.h>
+#include HASH_FUN_H                 // for hash<>
+_START_GOOGLE_NAMESPACE_
+
+template <class Value,
+          class HashFcn = SPARSEHASH_HASH<Value>,   // defined in sparseconfig.h
+          class EqualKey = std::equal_to<Value>,
+          class Alloc = libc_allocator_with_realloc<Value> >
+class dense_hash_set {
+ private:
+  // Apparently identity is not stl-standard, so we define our own
+  struct Identity {
+    typedef const Value& result_type;
+    const Value& operator()(const Value& v) const { return v; }
+  };
+  struct SetKey {
+    void operator()(Value* value, const Value& new_key) const {
+      *value = new_key;
+    }
+  };
+
+  // The actual data
+  typedef dense_hashtable<Value, Value, HashFcn, Identity, SetKey,
+                          EqualKey, Alloc> ht;
+  ht rep;
+
+ public:
+  typedef typename ht::key_type key_type;
+  typedef typename ht::value_type value_type;
+  typedef typename ht::hasher hasher;
+  typedef typename ht::key_equal key_equal;
+  typedef Alloc allocator_type;
+
+  typedef typename ht::size_type size_type;
+  typedef typename ht::difference_type difference_type;
+  typedef typename ht::const_pointer pointer;
+  typedef typename ht::const_pointer const_pointer;
+  typedef typename ht::const_reference reference;
+  typedef typename ht::const_reference const_reference;
+
+  typedef typename ht::const_iterator iterator;
+  typedef typename ht::const_iterator const_iterator;
+  typedef typename ht::const_local_iterator local_iterator;
+  typedef typename ht::const_local_iterator const_local_iterator;
+
+
+  // Iterator functions -- recall all iterators are const
+  iterator begin() const                  { return rep.begin(); }
+  iterator end() const                    { return rep.end(); }
+
+  // These come from tr1's unordered_set. For us, a bucket has 0 or 1 elements.
+  local_iterator begin(size_type i) const { return rep.begin(i); }
+  local_iterator end(size_type i) const   { return rep.end(i); }
+
+
+  // Accessor functions
+  allocator_type get_allocator() const    { return rep.get_allocator(); }
+  hasher hash_funct() const               { return rep.hash_funct(); }
+  hasher hash_function() const            { return hash_funct(); }  // tr1 name
+  key_equal key_eq() const                { return rep.key_eq(); }
+
+
+  // Constructors
+  explicit dense_hash_set(size_type expected_max_items_in_table = 0,
+                          const hasher& hf = hasher(),
+                          const key_equal& eql = key_equal(),
+                          const allocator_type& alloc = allocator_type())
+      : rep(expected_max_items_in_table, hf, eql, Identity(), SetKey(), alloc) {
+  }
+
+  template <class InputIterator>
+  dense_hash_set(InputIterator f, InputIterator l,
+                 const key_type& empty_key_val,
+                 size_type expected_max_items_in_table = 0,
+                 const hasher& hf = hasher(),
+                 const key_equal& eql = key_equal(),
+                 const allocator_type& alloc = allocator_type())
+      : rep(expected_max_items_in_table, hf, eql, Identity(), SetKey(), alloc) {
+    set_empty_key(empty_key_val);
+    rep.insert(f, l);
+  }
+  // We use the default copy constructor
+  // We use the default operator=()
+  // We use the default destructor
+
+  void clear()                        { rep.clear(); }
+  // This clears the hash set without resizing it down to the minimum
+  // bucket count, but rather keeps the number of buckets constant
+  void clear_no_resize()              { rep.clear_no_resize(); }
+  void swap(dense_hash_set& hs)       { rep.swap(hs.rep); }
+
+
+  // Functions concerning size
+  size_type size() const              { return rep.size(); }
+  size_type max_size() const          { return rep.max_size(); }
+  bool empty() const                  { return rep.empty(); }
+  size_type bucket_count() const      { return rep.bucket_count(); }
+  size_type max_bucket_count() const  { return rep.max_bucket_count(); }
+
+  // These are tr1 methods.  bucket() is the bucket the key is or would be in.
+  size_type bucket_size(size_type i) const    { return rep.bucket_size(i); }
+  size_type bucket(const key_type& key) const { return rep.bucket(key); }
+  float load_factor() const {
+    return size() * 1.0f / bucket_count();
+  }
+  float max_load_factor() const {
+    float shrink, grow;
+    rep.get_resizing_parameters(&shrink, &grow);
+    return grow;
+  }
+  void max_load_factor(float new_grow) {
+    float shrink, grow;
+    rep.get_resizing_parameters(&shrink, &grow);
+    rep.set_resizing_parameters(shrink, new_grow);
+  }
+  // These aren't tr1 methods but perhaps ought to be.
+  float min_load_factor() const {
+    float shrink, grow;
+    rep.get_resizing_parameters(&shrink, &grow);
+    return shrink;
+  }
+  void min_load_factor(float new_shrink) {
+    float shrink, grow;
+    rep.get_resizing_parameters(&shrink, &grow);
+    rep.set_resizing_parameters(new_shrink, grow);
+  }
+  // Deprecated; use min_load_factor() or max_load_factor() instead.
+  void set_resizing_parameters(float shrink, float grow) {
+    rep.set_resizing_parameters(shrink, grow);
+  }
+
+  void resize(size_type hint)         { rep.resize(hint); }
+  void rehash(size_type hint)         { resize(hint); }     // the tr1 name
+
+  // Lookup routines
+  iterator find(const key_type& key) const           { return rep.find(key); }
+
+  size_type count(const key_type& key) const         { return rep.count(key); }
+
+  std::pair<iterator, iterator> equal_range(const key_type& key) const {
+    return rep.equal_range(key);
+  }
+
+
+  // Insertion routines
+  std::pair<iterator, bool> insert(const value_type& obj) {
+    std::pair<typename ht::iterator, bool> p = rep.insert(obj);
+    return std::pair<iterator, bool>(p.first, p.second);   // const to non-const
+  }
+  template <class InputIterator> void insert(InputIterator f, InputIterator l) {
+    rep.insert(f, l);
+  }
+  void insert(const_iterator f, const_iterator l) {
+    rep.insert(f, l);
+  }
+  // Required for std::insert_iterator; the passed-in iterator is ignored.
+  iterator insert(iterator, const value_type& obj)   {
+    return insert(obj).first;
+  }
+
+  // Deletion and empty routines
+  // THESE ARE NON-STANDARD!  I make you specify an "impossible" key
+  // value to identify deleted and empty buckets.  You can change the
+  // deleted key as time goes on, or get rid of it entirely to be insert-only.
+  void set_empty_key(const key_type& key)     { rep.set_empty_key(key); }
+  key_type empty_key() const                  { return rep.empty_key(); }
+
+  void set_deleted_key(const key_type& key)   { rep.set_deleted_key(key); }
+  void clear_deleted_key()                    { rep.clear_deleted_key(); }
+  key_type deleted_key() const                { return rep.deleted_key(); }
+
+  // These are standard
+  size_type erase(const key_type& key)               { return rep.erase(key); }
+  void erase(iterator it)                            { rep.erase(it); }
+  void erase(iterator f, iterator l)                 { rep.erase(f, l); }
+
+
+  // Comparison
+  bool operator==(const dense_hash_set& hs) const    { return rep == hs.rep; }
+  bool operator!=(const dense_hash_set& hs) const    { return rep != hs.rep; }
+
+
+  // I/O -- this is an add-on for writing metainformation to disk
+  //
+  // For maximum flexibility, this does not assume a particular
+  // file type (though it will probably be a FILE *).  We just pass
+  // the fp through to rep.
+
+  // If your keys and values are simple enough, you can pass this
+  // serializer to serialize()/unserialize().  "Simple enough" means
+  // value_type is a POD type that contains no pointers.  Note,
+  // however, we don't try to normalize endianness.
+  typedef typename ht::NopointerSerializer NopointerSerializer;
+
+  // serializer: a class providing operator()(OUTPUT*, const value_type&)
+  //    (writing value_type to OUTPUT).  You can specify a
+  //    NopointerSerializer object if appropriate (see above).
+  // fp: either a FILE*, OR an ostream*/subclass_of_ostream*, OR a
+  //    pointer to a class providing size_t Write(const void*, size_t),
+  //    which writes a buffer into a stream (which fp presumably
+  //    owns) and returns the number of bytes successfully written.
+  //    Note basic_ostream<not_char> is not currently supported.
+  template <typename ValueSerializer, typename OUTPUT>
+  bool serialize(ValueSerializer serializer, OUTPUT* fp) {
+    return rep.serialize(serializer, fp);
+  }
+
+  // serializer: a functor providing operator()(INPUT*, value_type*)
+  //    (reading from INPUT and into value_type).  You can specify a
+  //    NopointerSerializer object if appropriate (see above).
+  // fp: either a FILE*, OR an istream*/subclass_of_istream*, OR a
+  //    pointer to a class providing size_t Read(void*, size_t),
+  //    which reads into a buffer from a stream (which fp presumably
+  //    owns) and returns the number of bytes successfully read.
+  //    Note basic_istream<not_char> is not currently supported.
+  template <typename ValueSerializer, typename INPUT>
+  bool unserialize(ValueSerializer serializer, INPUT* fp) {
+    return rep.unserialize(serializer, fp);
+  }
+};
+
+template <class Val, class HashFcn, class EqualKey, class Alloc>
+inline void swap(dense_hash_set<Val, HashFcn, EqualKey, Alloc>& hs1,
+                 dense_hash_set<Val, HashFcn, EqualKey, Alloc>& hs2) {
+  hs1.swap(hs2);
+}
+
+_END_GOOGLE_NAMESPACE_
+
+#endif /* _DENSE_HASH_SET_H_ */

+ 1547 - 0
library/cpp/porto/libporto.cpp

@@ -0,0 +1,1547 @@
+#include "libporto.hpp"
+#include "metrics.hpp"
+
+#include <google/protobuf/text_format.h>
+#include <google/protobuf/io/zero_copy_stream_impl.h>
+#include <google/protobuf/io/coded_stream.h>
+
+extern "C" {
+#include <errno.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#ifndef __linux__
+#include <fcntl.h>
+#else
+#include <sys/epoll.h>
+#endif
+}
+
+namespace Porto {
+
+TPortoApi::~TPortoApi() {
+    Disconnect();
+}
+
+EError TPortoApi::SetError(const TString &prefix, int _errno) {
+    LastErrorMsg = prefix + ": " + strerror(_errno);
+
+    switch (_errno) {
+        case ENOENT:
+            LastError = EError::SocketUnavailable;
+            break;
+        case EAGAIN:
+            LastErrorMsg = prefix + ": Timeout exceeded. Timeout value: " + std::to_string(Timeout);
+            LastError = EError::SocketTimeout;
+            break;
+        case EIO:
+        case EPIPE:
+            LastError = EError::SocketError;
+            break;
+        default:
+            LastError = EError::Unknown;
+            break;
+    }
+
+    Disconnect();
+    return LastError;
+}
+
+TString TPortoApi::GetLastError() const {
+    return EError_Name(LastError) + ":(" + LastErrorMsg + ")";
+}
+
+EError TPortoApi::Connect(const char *socket_path) {
+    struct sockaddr_un peer_addr;
+    socklen_t peer_addr_size;
+
+    Disconnect();
+
+#ifdef __linux__
+    Fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+    if (Fd < 0)
+        return SetError("socket", errno);
+#else
+    Fd = socket(AF_UNIX, SOCK_STREAM, 0);
+    if (Fd < 0)
+        return SetError("socket", errno);
+    if (fcntl(Fd, F_SETFD, FD_CLOEXEC) < 0)
+        return SetError("fcntl FD_CLOEXEC", errno);
+#endif
+
+    if (Timeout > 0 && SetSocketTimeout(3, Timeout))
+        return LastError;
+
+    memset(&peer_addr, 0, sizeof(struct sockaddr_un));
+    peer_addr.sun_family = AF_UNIX;
+    strncpy(peer_addr.sun_path, socket_path, strlen(socket_path));
+
+    peer_addr_size = sizeof(struct sockaddr_un);
+    if (connect(Fd, (struct sockaddr *) &peer_addr, peer_addr_size) < 0)
+        return SetError("connect", errno);
+
+    /* Restore async wait state */
+    if (!AsyncWaitNames.empty()) {
+        for (auto &name: AsyncWaitNames)
+            Req.mutable_asyncwait()->add_name(name);
+        for (auto &label: AsyncWaitLabels)
+            Req.mutable_asyncwait()->add_label(label);
+        if (AsyncWaitTimeout >= 0)
+            Req.mutable_asyncwait()->set_timeout_ms(AsyncWaitTimeout * 1000);
+        return Call();
+    }
+
+    return EError::Success;
+}
+
+void TPortoApi::Disconnect() {
+    if (Fd >= 0)
+        close(Fd);
+    Fd = -1;
+}
+
+EError TPortoApi::SetSocketTimeout(int direction, int timeout) {
+    struct timeval tv;
+
+    if (Fd < 0)
+        return EError::Success;
+
+    tv.tv_sec = timeout > 0 ? timeout : 0;
+    tv.tv_usec = 0;
+
+    if ((direction & 1) && setsockopt(Fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv))
+        return SetError("setsockopt SO_SNDTIMEO", errno);
+
+    if ((direction & 2) && setsockopt(Fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv))
+        return SetError("setsockopt SO_RCVTIMEO", errno);
+
+    return EError::Success;
+}
+
+EError TPortoApi::SetTimeout(int timeout) {
+    Timeout = timeout ? timeout : DEFAULT_TIMEOUT;
+    return SetSocketTimeout(3, Timeout);
+}
+
+EError TPortoApi::SetDiskTimeout(int timeout) {
+    DiskTimeout = timeout ? timeout : DEFAULT_DISK_TIMEOUT;
+    return EError::Success;
+}
+
+EError TPortoApi::Send(const TPortoRequest &req) {
+    google::protobuf::io::FileOutputStream raw(Fd);
+
+    if (!req.IsInitialized()) {
+        LastError = EError::InvalidMethod;
+        LastErrorMsg = "Request is not initialized";
+        return EError::InvalidMethod;
+    }
+
+    {
+        google::protobuf::io::CodedOutputStream output(&raw);
+
+        output.WriteVarint32(req.ByteSize());
+        req.SerializeWithCachedSizes(&output);
+    }
+
+    raw.Flush();
+
+    int err = raw.GetErrno();
+    if (err)
+        return SetError("send", err);
+
+    return EError::Success;
+}
+
+EError TPortoApi::Recv(TPortoResponse &rsp) {
+    google::protobuf::io::FileInputStream raw(Fd);
+    google::protobuf::io::CodedInputStream input(&raw);
+
+    while (true) {
+        uint32_t size;
+
+        if (!input.ReadVarint32(&size))
+            return SetError("recv", raw.GetErrno() ?: EIO);
+
+        auto prev_limit = input.PushLimit(size);
+
+        rsp.Clear();
+
+        if (!rsp.ParseFromCodedStream(&input))
+            return SetError("recv", raw.GetErrno() ?: EIO);
+
+        input.PopLimit(prev_limit);
+
+        if (rsp.has_asyncwait()) {
+            if (AsyncWaitCallback)
+                AsyncWaitCallback(rsp.asyncwait());
+
+            if (AsyncWaitOneShot)
+                return EError::Success;
+
+            continue;
+        }
+
+        return EError::Success;
+    }
+}
+
+EError TPortoApi::Call(const TPortoRequest &req,
+                        TPortoResponse &rsp,
+                        int extra_timeout) {
+    bool reconnect = AutoReconnect;
+    EError err = EError::Success;
+
+    if (Fd < 0) {
+        if (!reconnect)
+            return SetError("Not connected", EIO);
+        err = Connect();
+        reconnect = false;
+    }
+
+    if (!err) {
+        err = Send(req);
+        if (err == EError::SocketError && reconnect) {
+            err = Connect();
+            if (!err)
+                err = Send(req);
+        }
+    }
+
+    if (!err && extra_timeout && Timeout > 0)
+        err = SetSocketTimeout(2, extra_timeout > 0 ? (extra_timeout + Timeout) : -1);
+
+    if (!err)
+        err = Recv(rsp);
+
+    if (extra_timeout && Timeout > 0) {
+        EError err = SetSocketTimeout(2, Timeout);
+        (void)err;
+    }
+
+    if (!err) {
+        err = LastError = rsp.error();
+        LastErrorMsg = rsp.errormsg();
+    }
+
+    return err;
+}
+
+EError TPortoApi::Call(int extra_timeout) {
+    return Call(Req, Rsp, extra_timeout);
+}
+
+EError TPortoApi::Call(const TString &req,
+                        TString &rsp,
+                        int extra_timeout) {
+    Req.Clear();
+    if (!google::protobuf::TextFormat::ParseFromString(req, &Req)) {
+        LastError = EError::InvalidMethod;
+        LastErrorMsg = "Cannot parse request";
+        rsp = "";
+        return EError::InvalidMethod;
+    }
+
+    EError err = Call(Req, Rsp, extra_timeout);
+
+    rsp = Rsp.DebugString();
+
+    return err;
+}
+
+EError TPortoApi::GetVersion(TString &tag, TString &revision) {
+    Req.Clear();
+    Req.mutable_version();
+
+    if (!Call()) {
+        tag = Rsp.version().tag();
+        revision = Rsp.version().revision();
+    }
+
+    return LastError;
+}
+
+const TGetSystemResponse *TPortoApi::GetSystem() {
+    Req.Clear();
+    Req.mutable_getsystem();
+    if (!Call())
+        return &Rsp.getsystem();
+    return nullptr;
+}
+
+EError TPortoApi::SetSystem(const TString &key, const TString &val) {
+    TString rsp;
+    return Call("SetSystem {" + key + ":" + val + "}", rsp);
+}
+
+/* Container */
+
+EError TPortoApi::Create(const TString &name) {
+    Req.Clear();
+    auto req = Req.mutable_create();
+    req->set_name(name);
+    return Call();
+}
+
+EError TPortoApi::CreateWeakContainer(const TString &name) {
+    Req.Clear();
+    auto req = Req.mutable_createweak();
+    req->set_name(name);
+    return Call();
+}
+
+EError TPortoApi::Destroy(const TString &name) {
+    Req.Clear();
+    auto req = Req.mutable_destroy();
+    req->set_name(name);
+    return Call();
+}
+
+const TListResponse *TPortoApi::List(const TString &mask) {
+    Req.Clear();
+    auto req = Req.mutable_list();
+
+    if(!mask.empty())
+        req->set_mask(mask);
+
+    if (!Call())
+        return &Rsp.list();
+
+    return nullptr;
+}
+
+EError TPortoApi::List(TVector<TString> &list, const TString &mask) {
+    Req.Clear();
+    auto req = Req.mutable_list();
+    if(!mask.empty())
+        req->set_mask(mask);
+    if (!Call())
+        list = TVector<TString>(std::begin(Rsp.list().name()),
+                                        std::end(Rsp.list().name()));
+    return LastError;
+}
+
+const TListPropertiesResponse *TPortoApi::ListProperties() {
+    Req.Clear();
+    Req.mutable_listproperties();
+
+    if (Call())
+        return nullptr;
+
+    bool has_data = false;
+    for (const auto &prop: Rsp.listproperties().list()) {
+        if (prop.read_only()) {
+            has_data = true;
+            break;
+        }
+    }
+
+    if (!has_data) {
+        TPortoRequest req;
+        TPortoResponse rsp;
+
+        req.mutable_listdataproperties();
+        if (!Call(req, rsp)) {
+            for (const auto &data: rsp.listdataproperties().list()) {
+                auto d = Rsp.mutable_listproperties()->add_list();
+                d->set_name(data.name());
+                d->set_desc(data.desc());
+                d->set_read_only(true);
+            }
+        }
+    }
+
+    return &Rsp.listproperties();
+}
+
+EError TPortoApi::ListProperties(TVector<TString> &properties) {
+    properties.clear();
+    auto rsp = ListProperties();
+    if (rsp) {
+        for (auto &prop: rsp->list())
+            properties.push_back(prop.name());
+    }
+    return LastError;
+}
+
+const TGetResponse *TPortoApi::Get(const TVector<TString> &names,
+                                   const TVector<TString> &vars,
+                                   int flags) {
+    Req.Clear();
+    auto get = Req.mutable_get();
+
+    for (const auto &n : names)
+        get->add_name(n);
+
+    for (const auto &v : vars)
+        get->add_variable(v);
+
+    if (flags & GET_NONBLOCK)
+        get->set_nonblock(true);
+    if (flags & GET_SYNC)
+        get->set_sync(true);
+    if (flags & GET_REAL)
+        get->set_real(true);
+
+    if (!Call())
+        return &Rsp.get();
+
+    return nullptr;
+}
+
+EError TPortoApi::GetContainerSpec(const TString &name, TContainer &container) {
+    Req.Clear();
+    TListContainersRequest req;
+    auto filter = req.add_filters();
+    filter->set_name(name);
+
+    TVector<TContainer> containers;
+
+    auto ret = ListContainersBy(req, containers);
+    if (containers.empty())
+        return EError::ContainerDoesNotExist;
+
+    if (!ret)
+        container = containers[0];
+
+    return ret;
+}
+
+EError TPortoApi::ListContainersBy(const TListContainersRequest &listContainersRequest, TVector<TContainer> &containers) {
+    Req.Clear();
+    auto req = Req.mutable_listcontainersby();
+    *req = listContainersRequest;
+
+    auto ret = Call();
+    if (ret)
+        return ret;
+
+    for (auto &ct : Rsp.listcontainersby().containers())
+        containers.push_back(ct);
+
+    return EError::Success;
+}
+
+EError TPortoApi::CreateFromSpec(const TContainerSpec &container, TVector<TVolumeSpec> volumes, bool start) {
+    Req.Clear();
+    auto req = Req.mutable_createfromspec();
+
+    auto ct = req->mutable_container();
+    *ct = container;
+
+    for  (auto &volume : volumes) {
+        auto v = req->add_volumes();
+        *v = volume;
+    }
+
+    req->set_start(start);
+
+    return Call();
+}
+
+EError TPortoApi::UpdateFromSpec(const TContainerSpec &container) {
+    Req.Clear();
+    auto req = Req.mutable_updatefromspec();
+
+    auto ct = req->mutable_container();
+    *ct = container;
+
+    return Call();
+}
+
+EError TPortoApi::GetProperty(const TString &name,
+                              const TString &property,
+                              TString &value,
+                              int flags) {
+    Req.Clear();
+    auto req = Req.mutable_getproperty();
+
+    req->set_name(name);
+    req->set_property(property);
+    if (flags & GET_SYNC)
+        req->set_sync(true);
+    if (flags & GET_REAL)
+        req->set_real(true);
+
+    if (!Call())
+        value = Rsp.getproperty().value();
+
+    return LastError;
+}
+
+EError TPortoApi::SetProperty(const TString &name,
+                              const TString &property,
+                              const TString &value) {
+    Req.Clear();
+    auto req = Req.mutable_setproperty();
+
+    req->set_name(name);
+    req->set_property(property);
+    req->set_value(value);
+
+    return Call();
+}
+
+EError TPortoApi::GetInt(const TString &name,
+                         const TString &property,
+                         const TString &index,
+                         uint64_t &value) {
+    TString key = property, str;
+    if (index.size())
+        key = property + "[" + index + "]";
+    if (!GetProperty(name, key, str)) {
+        const char *ptr = str.c_str();
+        char *end;
+        errno = 0;
+        value = strtoull(ptr, &end, 10);
+        if (errno || end == ptr || *end) {
+            LastError = EError::InvalidValue;
+            LastErrorMsg = " value: " + str;
+        }
+    }
+    return LastError;
+}
+
+EError TPortoApi::SetInt(const TString &name,
+                         const TString &property,
+                         const TString &index,
+                         uint64_t value) {
+    TString key = property;
+    if (index.size())
+        key = property + "[" + index + "]";
+    return SetProperty(name, key, ToString(value));
+}
+
+EError TPortoApi::GetProcMetric(const TVector<TString> &names,
+                                const TString &metric,
+                                TMap<TString, uint64_t> &values) {
+    auto it = ProcMetrics.find(metric);
+
+    if (it == ProcMetrics.end()) {
+        LastError = EError::InvalidValue;
+        LastErrorMsg = " Unknown metric: " + metric;
+        return LastError;
+    }
+
+    LastError = it->second->GetValues(names, values, *this);
+
+    if (LastError)
+        LastErrorMsg = "Unknown error on Get() method";
+
+    return LastError;
+}
+
+EError TPortoApi::SetLabel(const TString &name,
+                           const TString &label,
+                           const TString &value,
+                           const TString &prev_value) {
+    Req.Clear();
+    auto req = Req.mutable_setlabel();
+
+    req->set_name(name);
+    req->set_label(label);
+    req->set_value(value);
+    if (prev_value != " ")
+        req->set_prev_value(prev_value);
+
+    return Call();
+}
+
+EError TPortoApi::IncLabel(const TString &name,
+                           const TString &label,
+                           int64_t add,
+                           int64_t &result) {
+    Req.Clear();
+    auto req = Req.mutable_inclabel();
+
+    req->set_name(name);
+    req->set_label(label);
+    req->set_add(add);
+
+    EError err = Call();
+
+    if (Rsp.has_inclabel())
+        result = Rsp.inclabel().result();
+
+    return err;
+}
+
+EError TPortoApi::Start(const TString &name) {
+    Req.Clear();
+    auto req = Req.mutable_start();
+
+    req->set_name(name);
+
+    return Call();
+}
+
+EError TPortoApi::Stop(const TString &name, int stop_timeout) {
+    Req.Clear();
+    auto req = Req.mutable_stop();
+
+    req->set_name(name);
+    if (stop_timeout >= 0)
+        req->set_timeout_ms(stop_timeout * 1000);
+
+    return Call(stop_timeout > 0 ? stop_timeout : 0);
+}
+
+EError TPortoApi::Kill(const TString &name, int sig) {
+    Req.Clear();
+    auto req = Req.mutable_kill();
+
+    req->set_name(name);
+    req->set_sig(sig);
+
+    return Call();
+}
+
+EError TPortoApi::Pause(const TString &name) {
+    Req.Clear();
+    auto req = Req.mutable_pause();
+
+    req->set_name(name);
+
+    return Call();
+}
+
+EError TPortoApi::Resume(const TString &name) {
+    Req.Clear();
+    auto req = Req.mutable_resume();
+
+    req->set_name(name);
+
+    return Call();
+}
+
+EError TPortoApi::Respawn(const TString &name) {
+    Req.Clear();
+    auto req = Req.mutable_respawn();
+
+    req->set_name(name);
+
+    return Call();
+}
+
+EError TPortoApi::CallWait(TString &result_state, int wait_timeout) {
+    time_t deadline = 0;
+    time_t last_retry = 0;
+
+    if (wait_timeout >= 0) {
+        deadline = time(nullptr) + wait_timeout;
+        Req.mutable_wait()->set_timeout_ms(wait_timeout * 1000);
+    }
+
+retry:
+    if (!Call(wait_timeout)) {
+        if (Rsp.wait().has_state())
+            result_state = Rsp.wait().state();
+        else if (Rsp.wait().name() == "")
+            result_state = "timeout";
+        else
+            result_state = "dead";
+    } else if (LastError == EError::SocketError && AutoReconnect) {
+        time_t now = time(nullptr);
+
+        if (wait_timeout < 0 || now < deadline) {
+            if (wait_timeout >= 0) {
+                wait_timeout = deadline - now;
+                Req.mutable_wait()->set_timeout_ms(wait_timeout * 1000);
+            }
+            if (last_retry == now)
+                sleep(1);
+            last_retry = now;
+            goto retry;
+        }
+
+        result_state = "timeout";
+    } else
+        result_state = "unknown";
+
+    return LastError;
+}
+
+EError TPortoApi::WaitContainer(const TString &name,
+                                TString &result_state,
+                                int wait_timeout) {
+    Req.Clear();
+    auto req = Req.mutable_wait();
+
+    req->add_name(name);
+
+    return CallWait(result_state, wait_timeout);
+}
+
+EError TPortoApi::WaitContainers(const TVector<TString> &names,
+                                 TString &result_name,
+                                 TString &result_state,
+                                 int wait_timeout) {
+    Req.Clear();
+    auto req = Req.mutable_wait();
+
+    for (auto &c : names)
+        req->add_name(c);
+
+    EError err = CallWait(result_state, wait_timeout);
+
+    result_name = Rsp.wait().name();
+
+    return err;
+}
+
+const TWaitResponse *TPortoApi::Wait(const TVector<TString> &names,
+                                     const TVector<TString> &labels,
+                                     int wait_timeout) {
+    Req.Clear();
+    auto req = Req.mutable_wait();
+    TString result_state;
+
+    for (auto &c : names)
+        req->add_name(c);
+    for (auto &label: labels)
+        req->add_label(label);
+
+    EError err = CallWait(result_state, wait_timeout);
+    (void)err;
+
+    if (Rsp.has_wait())
+        return &Rsp.wait();
+
+    return nullptr;
+}
+
+EError TPortoApi::AsyncWait(const TVector<TString> &names,
+                             const TVector<TString> &labels,
+                             TWaitCallback callback,
+                             int wait_timeout,
+                             const TString &targetState) {
+    Req.Clear();
+    auto req = Req.mutable_asyncwait();
+
+    AsyncWaitNames.clear();
+    AsyncWaitLabels.clear();
+    AsyncWaitTimeout = wait_timeout;
+    AsyncWaitCallback = callback;
+
+    for (auto &name: names)
+        req->add_name(name);
+    for (auto &label: labels)
+        req->add_label(label);
+    if (wait_timeout >= 0)
+        req->set_timeout_ms(wait_timeout * 1000);
+    if (!targetState.empty()) {
+        req->set_target_state(targetState);
+        AsyncWaitOneShot = true;
+    } else
+        AsyncWaitOneShot = false;
+
+    if (Call()) {
+        AsyncWaitCallback = nullptr;
+    } else {
+        AsyncWaitNames = names;
+        AsyncWaitLabels = labels;
+    }
+
+    return LastError;
+}
+
+EError TPortoApi::StopAsyncWait(const TVector<TString> &names,
+                                const TVector<TString> &labels,
+                                const TString &targetState) {
+    Req.Clear();
+    auto req = Req.mutable_stopasyncwait();
+
+    AsyncWaitNames.clear();
+    AsyncWaitLabels.clear();
+
+    for (auto &name: names)
+        req->add_name(name);
+    for (auto &label: labels)
+        req->add_label(label);
+    if (!targetState.empty()) {
+        req->set_target_state(targetState);
+    }
+
+    return Call();
+}
+
+EError TPortoApi::ConvertPath(const TString &path,
+                              const TString &src,
+                              const TString &dest,
+                              TString &res) {
+    Req.Clear();
+    auto req = Req.mutable_convertpath();
+
+    req->set_path(path);
+    req->set_source(src);
+    req->set_destination(dest);
+
+    if (!Call())
+        res = Rsp.convertpath().path();
+
+    return LastError;
+}
+
+EError TPortoApi::AttachProcess(const TString &name, int pid,
+                                const TString &comm) {
+    Req.Clear();
+    auto req = Req.mutable_attachprocess();
+
+    req->set_name(name);
+    req->set_pid(pid);
+    req->set_comm(comm);
+
+    return Call();
+}
+
+EError TPortoApi::AttachThread(const TString &name, int pid,
+                               const TString &comm) {
+    Req.Clear();
+    auto req = Req.mutable_attachthread();
+
+    req->set_name(name);
+    req->set_pid(pid);
+    req->set_comm(comm);
+
+    return Call();
+}
+
+EError TPortoApi::LocateProcess(int pid, const TString &comm,
+                                TString &name) {
+    Req.Clear();
+    auto req = Req.mutable_locateprocess();
+
+    req->set_pid(pid);
+    req->set_comm(comm);
+
+    if (!Call())
+        name = Rsp.locateprocess().name();
+
+    return LastError;
+}
+
+/* Volume */
+
+const TListVolumePropertiesResponse *TPortoApi::ListVolumeProperties() {
+    Req.Clear();
+    Req.mutable_listvolumeproperties();
+
+    if (!Call())
+        return &Rsp.listvolumeproperties();
+
+    return nullptr;
+}
+
+EError TPortoApi::ListVolumeProperties(TVector<TString> &properties) {
+    properties.clear();
+    auto rsp = ListVolumeProperties();
+    if (rsp) {
+        for (auto &prop: rsp->list())
+            properties.push_back(prop.name());
+    }
+    return LastError;
+}
+
+EError TPortoApi::CreateVolume(TString &path,
+                               const TMap<TString, TString> &config) {
+    Req.Clear();
+    auto req = Req.mutable_createvolume();
+
+    req->set_path(path);
+
+    *(req->mutable_properties()) =
+        google::protobuf::Map<TString, TString>(config.begin(), config.end());
+
+    if (!Call(DiskTimeout) && path.empty())
+        path = Rsp.createvolume().path();
+
+    return LastError;
+}
+
+EError TPortoApi::TuneVolume(const TString &path,
+                             const TMap<TString, TString> &config) {
+    Req.Clear();
+    auto req = Req.mutable_tunevolume();
+
+    req->set_path(path);
+
+    *(req->mutable_properties()) =
+        google::protobuf::Map<TString, TString>(config.begin(), config.end());
+
+    return Call(DiskTimeout);
+}
+
+EError TPortoApi::LinkVolume(const TString &path,
+                             const TString &container,
+                             const TString &target,
+                             bool read_only,
+                             bool required) {
+    Req.Clear();
+    auto req = (target.empty() && !required) ? Req.mutable_linkvolume() :
+                                               Req.mutable_linkvolumetarget();
+
+    req->set_path(path);
+    if (!container.empty())
+        req->set_container(container);
+    if (target != "")
+        req->set_target(target);
+    if (read_only)
+        req->set_read_only(read_only);
+    if (required)
+        req->set_required(required);
+
+    return Call();
+}
+
+EError TPortoApi::UnlinkVolume(const TString &path,
+                               const TString &container,
+                               const TString &target,
+                               bool strict) {
+    Req.Clear();
+    auto req = (target == "***") ? Req.mutable_unlinkvolume() :
+                                   Req.mutable_unlinkvolumetarget();
+
+    req->set_path(path);
+    if (!container.empty())
+        req->set_container(container);
+    if (target != "***")
+        req->set_target(target);
+    if (strict)
+        req->set_strict(strict);
+
+    return Call(DiskTimeout);
+}
+
+const TListVolumesResponse *
+TPortoApi::ListVolumes(const TString &path,
+                       const TString &container) {
+    Req.Clear();
+    auto req = Req.mutable_listvolumes();
+
+    if (!path.empty())
+        req->set_path(path);
+
+    if (!container.empty())
+        req->set_container(container);
+
+    if (Call())
+        return nullptr;
+
+    auto list = Rsp.mutable_listvolumes();
+
+    /* compat */
+    for (auto v: *list->mutable_volumes()) {
+        if (v.links().size())
+            break;
+        for (auto &ct: v.containers())
+            v.add_links()->set_container(ct);
+    }
+
+    return list;
+}
+
+EError TPortoApi::ListVolumes(TVector<TString> &paths) {
+    Req.Clear();
+    auto rsp = ListVolumes();
+    paths.clear();
+    if (rsp) {
+        for (auto &v : rsp->volumes())
+            paths.push_back(v.path());
+    }
+    return LastError;
+}
+
+const TVolumeDescription *TPortoApi::GetVolumeDesc(const TString &path) {
+    Req.Clear();
+    auto rsp = ListVolumes(path);
+
+    if (rsp && rsp->volumes().size())
+        return &rsp->volumes(0);
+
+    return nullptr;
+}
+
+const TVolumeSpec *TPortoApi::GetVolume(const TString &path) {
+    Req.Clear();
+    auto req = Req.mutable_getvolume();
+
+    req->add_path(path);
+
+    if (!Call() && Rsp.getvolume().volume().size())
+        return &Rsp.getvolume().volume(0);
+
+    return nullptr;
+}
+
+const TGetVolumeResponse *TPortoApi::GetVolumes(uint64_t changed_since) {
+    Req.Clear();
+    auto req = Req.mutable_getvolume();
+
+    if (changed_since)
+        req->set_changed_since(changed_since);
+
+    if (!Call() && Rsp.has_getvolume())
+        return &Rsp.getvolume();
+
+    return nullptr;
+}
+
+
+EError TPortoApi::ListVolumesBy(const TGetVolumeRequest &getVolumeRequest, TVector<TVolumeSpec> &volumes) {
+    Req.Clear();
+    auto req = Req.mutable_getvolume();
+    *req = getVolumeRequest;
+
+    auto ret = Call();
+    if (ret)
+        return ret;
+
+    for (auto volume : Rsp.getvolume().volume())
+        volumes.push_back(volume);
+    return EError::Success;
+}
+
+EError TPortoApi::CreateVolumeFromSpec(const TVolumeSpec &volume, TVolumeSpec &resultSpec) {
+    Req.Clear();
+    auto req = Req.mutable_newvolume();
+    auto vol = req->mutable_volume();
+    *vol = volume;
+
+    auto ret = Call();
+    if (ret)
+        return ret;
+
+    resultSpec =  Rsp.newvolume().volume();
+
+    return ret;
+}
+
+/* Layer */
+
+EError TPortoApi::ImportLayer(const TString &layer,
+                              const TString &tarball,
+                              bool merge,
+                              const TString &place,
+                              const TString &private_value,
+                              bool verboseError) {
+    Req.Clear();
+    auto req = Req.mutable_importlayer();
+
+    req->set_layer(layer);
+    req->set_tarball(tarball);
+    req->set_merge(merge);
+    req->set_verbose_error(verboseError);
+    if (place.size())
+        req->set_place(place);
+    if (private_value.size())
+        req->set_private_value(private_value);
+
+    return Call(DiskTimeout);
+}
+
+EError TPortoApi::ExportLayer(const TString &volume,
+                              const TString &tarball,
+                              const TString &compress) {
+    Req.Clear();
+    auto req = Req.mutable_exportlayer();
+
+    req->set_volume(volume);
+    req->set_tarball(tarball);
+    if (compress.size())
+        req->set_compress(compress);
+
+    return Call(DiskTimeout);
+}
+
+EError TPortoApi::ReExportLayer(const TString &layer,
+                                const TString &tarball,
+                                const TString &compress) {
+    Req.Clear();
+    auto req = Req.mutable_exportlayer();
+
+    req->set_volume("");
+    req->set_layer(layer);
+    req->set_tarball(tarball);
+    if (compress.size())
+        req->set_compress(compress);
+
+    return Call(DiskTimeout);
+}
+
+EError TPortoApi::RemoveLayer(const TString &layer,
+                              const TString &place,
+                              bool async) {
+    Req.Clear();
+    auto req = Req.mutable_removelayer();
+
+    req->set_layer(layer);
+    req->set_async(async);
+    if (place.size())
+        req->set_place(place);
+
+    return Call(DiskTimeout);
+}
+
+const TListLayersResponse *TPortoApi::ListLayers(const TString &place,
+                                                 const TString &mask) {
+    Req.Clear();
+    auto req = Req.mutable_listlayers();
+
+    if (place.size())
+        req->set_place(place);
+    if (mask.size())
+        req->set_mask(mask);
+
+    if (Call())
+        return nullptr;
+
+    auto list = Rsp.mutable_listlayers();
+
+    /* compat conversion */
+    if (!list->layers().size() && list->layer().size()) {
+        for (auto &name: list->layer()) {
+            auto l = list->add_layers();
+            l->set_name(name);
+            l->set_owner_user("");
+            l->set_owner_group("");
+            l->set_last_usage(0);
+            l->set_private_value("");
+        }
+    }
+
+    return list;
+}
+
+EError TPortoApi::ListLayers(TVector<TString> &layers,
+                             const TString &place,
+                             const TString &mask) {
+    Req.Clear();
+    auto req = Req.mutable_listlayers();
+
+    if (place.size())
+        req->set_place(place);
+    if (mask.size())
+        req->set_mask(mask);
+
+    if (!Call())
+        layers = TVector<TString>(std::begin(Rsp.listlayers().layer()),
+                                      std::end(Rsp.listlayers().layer()));
+
+    return LastError;
+}
+
+EError TPortoApi::GetLayerPrivate(TString &private_value,
+                                  const TString &layer,
+                                  const TString &place) {
+    Req.Clear();
+    auto req = Req.mutable_getlayerprivate();
+
+    req->set_layer(layer);
+    if (place.size())
+        req->set_place(place);
+
+    if (!Call())
+        private_value = Rsp.getlayerprivate().private_value();
+
+    return LastError;
+}
+
+EError TPortoApi::SetLayerPrivate(const TString &private_value,
+                                  const TString &layer,
+                                  const TString &place) {
+    Req.Clear();
+    auto req = Req.mutable_setlayerprivate();
+
+    req->set_layer(layer);
+    req->set_private_value(private_value);
+    if (place.size())
+        req->set_place(place);
+
+    return Call();
+}
+
+/* Docker images */
+
+DockerImage::DockerImage(const TDockerImage &i) {
+    Id = i.id();
+    for (const auto &tag: i.tags())
+        Tags.emplace_back(tag);
+    for (const auto &digest: i.digests())
+        Digests.emplace_back(digest);
+    for (const auto &layer: i.layers())
+        Layers.emplace_back(layer);
+    if (i.has_size())
+        Size = i.size();
+    if (i.has_config()) {
+        auto &cfg = i.config();
+        for (const auto &cmd: cfg.cmd())
+            Config.Cmd.emplace_back(cmd);
+        for (const auto &env: cfg.env())
+            Config.Env.emplace_back(env);
+    }
+}
+
+EError TPortoApi::DockerImageStatus(DockerImage &image,
+                                     const TString &name,
+                                     const TString &place) {
+    auto req = Req.mutable_dockerimagestatus();
+    req->set_name(name);
+    if (!place.empty())
+        req->set_place(place);
+    EError ret = Call();
+    if (!ret && Rsp.dockerimagestatus().has_image())
+        image = DockerImage(Rsp.dockerimagestatus().image());
+    return ret;
+}
+
+EError TPortoApi::ListDockerImages(std::vector<DockerImage> &images,
+                                    const TString &place,
+                                    const TString &mask) {
+    auto req = Req.mutable_listdockerimages();
+    if (place.size())
+        req->set_place(place);
+    if (mask.size())
+        req->set_mask(mask);
+    EError ret = Call();
+    if (!ret) {
+        for (const auto &i: Rsp.listdockerimages().images())
+            images.emplace_back(i);
+    }
+    return ret;
+}
+
+EError TPortoApi::PullDockerImage(DockerImage &image,
+                                   const TString &name,
+                                   const TString &place,
+                                   const TString &auth_token,
+                                   const TString &auth_path,
+                                   const TString &auth_service) {
+    auto req = Req.mutable_pulldockerimage();
+    req->set_name(name);
+    if (place.size())
+        req->set_place(place);
+    if (auth_token.size())
+        req->set_auth_token(auth_token);
+    if (auth_path.size())
+        req->set_auth_path(auth_path);
+    if (auth_service.size())
+        req->set_auth_service(auth_service);
+    EError ret = Call();
+    if (!ret && Rsp.pulldockerimage().has_image())
+        image = DockerImage(Rsp.pulldockerimage().image());
+    return ret;
+}
+
+EError TPortoApi::RemoveDockerImage(const TString &name,
+                                     const TString &place) {
+    auto req = Req.mutable_removedockerimage();
+    req->set_name(name);
+    if (place.size())
+        req->set_place(place);
+    return Call();
+}
+
+/* Storage */
+
+const TListStoragesResponse *TPortoApi::ListStorages(const TString &place,
+                                                     const TString &mask) {
+    Req.Clear();
+    auto req = Req.mutable_liststorages();
+
+    if (place.size())
+        req->set_place(place);
+    if (mask.size())
+        req->set_mask(mask);
+
+    if (Call())
+        return nullptr;
+
+    return &Rsp.liststorages();
+}
+
+EError TPortoApi::ListStorages(TVector<TString> &storages,
+                               const TString &place,
+                               const TString &mask) {
+    Req.Clear();
+    auto req = Req.mutable_liststorages();
+
+    if (place.size())
+        req->set_place(place);
+    if (mask.size())
+        req->set_mask(mask);
+
+    if (!Call()) {
+        storages.clear();
+        for (auto &storage: Rsp.liststorages().storages())
+            storages.push_back(storage.name());
+    }
+
+    return LastError;
+}
+
+EError TPortoApi::RemoveStorage(const TString &storage,
+                                const TString &place) {
+    Req.Clear();
+    auto req = Req.mutable_removestorage();
+
+    req->set_name(storage);
+    if (place.size())
+        req->set_place(place);
+
+    return Call(DiskTimeout);
+}
+
+EError TPortoApi::ImportStorage(const TString &storage,
+                                const TString &archive,
+                                const TString &place,
+                                const TString &compression,
+                                const TString &private_value) {
+    Req.Clear();
+    auto req = Req.mutable_importstorage();
+
+    req->set_name(storage);
+    req->set_tarball(archive);
+    if (place.size())
+        req->set_place(place);
+    if (compression.size())
+        req->set_compress(compression);
+    if (private_value.size())
+        req->set_private_value(private_value);
+
+    return Call(DiskTimeout);
+}
+
+EError TPortoApi::ExportStorage(const TString &storage,
+                                const TString &archive,
+                                const TString &place,
+                                const TString &compression) {
+    Req.Clear();
+    auto req = Req.mutable_exportstorage();
+
+    req->set_name(storage);
+    req->set_tarball(archive);
+    if (place.size())
+        req->set_place(place);
+    if (compression.size())
+        req->set_compress(compression);
+
+    return Call(DiskTimeout);
+}
+
+#ifdef __linux__
+void TAsyncWaiter::MainCallback(const TWaitResponse &event) {
+    CallbacksCount++;
+
+    auto it = AsyncCallbacks.find(event.name());
+    if (it != AsyncCallbacks.end() && it->second.State == event.state()) {
+        it->second.Callback(event);
+        AsyncCallbacks.erase(it);
+    }
+}
+
+int TAsyncWaiter::Repair() {
+    for (const auto &it : AsyncCallbacks) {
+        int ret = Api.AsyncWait({it.first}, {}, GetMainCallback(), -1, it.second.State);
+        if (ret)
+            return ret;
+    }
+    return 0;
+}
+
+void TAsyncWaiter::WatchDog() {
+    int ret;
+    auto apiFd = Api.Fd;
+
+    while (true) {
+        struct epoll_event events[2];
+        int nfds = epoll_wait(EpollFd, events, 2, -1);
+
+        if (nfds < 0) {
+            if (errno == EINTR)
+                continue;
+
+            Fatal("Can not make epoll_wait", errno);
+            return;
+        }
+
+        for (int n = 0; n < nfds; ++n) {
+            if (events[n].data.fd == apiFd) {
+                TPortoResponse rsp;
+                ret = Api.Recv(rsp);
+                // portod reloaded - async_wait must be repaired
+                if (ret == EError::SocketError) {
+                    ret = Api.Connect();
+                    if (ret) {
+                        Fatal("Can not connect to porto api", ret);
+                        return;
+                    }
+
+                    ret = Repair();
+                    if (ret) {
+                        Fatal("Can not repair", ret);
+                        return;
+                    }
+
+                    apiFd = Api.Fd;
+
+                    struct epoll_event portoEv;
+                    portoEv.events = EPOLLIN;
+                    portoEv.data.fd = apiFd;
+                    if (epoll_ctl(EpollFd, EPOLL_CTL_ADD, apiFd, &portoEv)) {
+                        Fatal("Can not epoll_ctl", errno);
+                        return;
+                    }
+                }
+            } else if (events[n].data.fd == Sock) {
+                ERequestType requestType = static_cast<ERequestType>(RecvInt(Sock));
+
+                switch (requestType) {
+                    case ERequestType::Add:
+                        HandleAddRequest();
+                        break;
+                    case ERequestType::Del:
+                        HandleDelRequest();
+                        break;
+                    case ERequestType::Stop:
+                        return;
+                    case ERequestType::None:
+                    default:
+                        Fatal("Unknown request", static_cast<int>(requestType));
+                }
+            }
+        }
+    }
+}
+
+void TAsyncWaiter::SendInt(int fd, int value) {
+    int ret = write(fd, &value, sizeof(value));
+    if (ret != sizeof(value))
+        Fatal("Can not send int", errno);
+}
+
+int TAsyncWaiter::RecvInt(int fd) {
+    int value;
+    int ret = read(fd, &value, sizeof(value));
+    if (ret != sizeof(value))
+        Fatal("Can not recv int", errno);
+
+    return value;
+}
+
+void TAsyncWaiter::HandleAddRequest() {
+    int ret = 0;
+
+    auto it = AsyncCallbacks.find(ReqCt);
+    if (it != AsyncCallbacks.end()) {
+        ret = Api.StopAsyncWait({ReqCt}, {}, it->second.State);
+        AsyncCallbacks.erase(it);
+    }
+
+    AsyncCallbacks.insert(std::make_pair(ReqCt, TCallbackData({ReqCallback, ReqState})));
+
+    ret = Api.AsyncWait({ReqCt}, {}, GetMainCallback(), -1, ReqState);
+    SendInt(Sock, ret);
+}
+
+void TAsyncWaiter::HandleDelRequest() {
+    int ret = 0;
+
+    auto it = AsyncCallbacks.find(ReqCt);
+    if (it != AsyncCallbacks.end()) {
+        ret = Api.StopAsyncWait({ReqCt}, {}, it->second.State);
+        AsyncCallbacks.erase(it);
+    }
+
+    SendInt(Sock, ret);
+}
+
+TAsyncWaiter::TAsyncWaiter(std::function<void(const TString &error, int ret)> fatalCallback)
+    : CallbacksCount(0ul)
+      , FatalCallback(fatalCallback)
+{
+    int socketPair[2];
+    int ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, socketPair);
+    if (ret) {
+        Fatal("Can not make socketpair", ret);
+        return;
+    }
+
+    MasterSock = socketPair[0];
+    Sock = socketPair[1];
+
+    ret = Api.Connect();
+    if (ret) {
+        Fatal("Can not connect to porto api", ret);
+        return;
+    }
+
+    auto apiFd = Api.Fd;
+
+    EpollFd = epoll_create1(EPOLL_CLOEXEC);
+
+    if (EpollFd == -1) {
+        Fatal("Can not epoll_create", errno);
+        return;
+    }
+
+    struct epoll_event pairEv;
+    pairEv.events = EPOLLIN;
+    pairEv.data.fd = Sock;
+
+    struct epoll_event portoEv;
+    portoEv.events = EPOLLIN;
+    portoEv.data.fd = apiFd;
+
+    if (epoll_ctl(EpollFd, EPOLL_CTL_ADD, Sock, &pairEv)) {
+        Fatal("Can not epoll_ctl", errno);
+        return;
+    }
+
+    if (epoll_ctl(EpollFd, EPOLL_CTL_ADD, apiFd, &portoEv)) {
+        Fatal("Can not epoll_ctl", errno);
+        return;
+    }
+
+    WatchDogThread = std::unique_ptr<std::thread>(new std::thread(&TAsyncWaiter::WatchDog, this));
+}
+
+TAsyncWaiter::~TAsyncWaiter() {
+    SendInt(MasterSock, static_cast<int>(ERequestType::Stop));
+    WatchDogThread->join();
+
+    // pedantic check, that porto api is watching by epoll
+    if (epoll_ctl(EpollFd, EPOLL_CTL_DEL, Api.Fd, nullptr) || epoll_ctl(EpollFd, EPOLL_CTL_DEL, Sock, nullptr)) {
+        Fatal("Can not epoll_ctl_del", errno);
+    }
+
+    close(EpollFd);
+    close(Sock);
+    close(MasterSock);
+}
+
+int TAsyncWaiter::Add(const TString &ct, const TString &state, TWaitCallback callback) {
+    if (FatalError)
+        return -1;
+
+    ReqCt = ct;
+    ReqState = state;
+    ReqCallback = callback;
+
+    SendInt(MasterSock, static_cast<int>(ERequestType::Add));
+    return RecvInt(MasterSock);
+}
+
+int TAsyncWaiter::Remove(const TString &ct) {
+    if (FatalError)
+        return -1;
+
+    ReqCt = ct;
+
+    SendInt(MasterSock, static_cast<int>(ERequestType::Del));
+    return RecvInt(MasterSock);
+}
+#endif
+
+} /* namespace Porto */

+ 492 - 0
library/cpp/porto/libporto.hpp

@@ -0,0 +1,492 @@
+#pragma once
+
+#include <atomic>
+#include <thread>
+
+#include <util/string/cast.h>
+#include <util/generic/hash.h>
+#include <util/generic/map.h>
+#include <util/generic/vector.h>
+
+#include <library/cpp/porto/proto/rpc.pb.h>
+
+namespace Porto {
+
+constexpr int INFINITE_TIMEOUT = -1;
+constexpr int DEFAULT_TIMEOUT = 300;        // 5min
+constexpr int DEFAULT_DISK_TIMEOUT = 900;   // 15min
+
+constexpr char SOCKET_PATH[] = "/run/portod.socket";
+
+typedef std::function<void(const TWaitResponse &event)> TWaitCallback;
+
+enum {
+    GET_NONBLOCK = 1,   // try lock container state
+    GET_SYNC = 2,       // refresh cached values, cache ttl 5s
+    GET_REAL = 4,       // no faked or inherited values
+};
+
+struct DockerImage {
+    std::string Id;
+    std::vector<std::string> Tags;
+    std::vector<std::string> Digests;
+    std::vector<std::string> Layers;
+    uint64_t Size;
+    struct Config {
+        std::vector<std::string> Cmd;
+        std::vector<std::string> Env;
+    } Config;
+
+    DockerImage() = default;
+    DockerImage(const TDockerImage &i);
+
+    DockerImage(const DockerImage &i) = default;
+    DockerImage(DockerImage &&i) = default;
+
+    DockerImage& operator=(const DockerImage &i) = default;
+    DockerImage& operator=(DockerImage &&i) = default;
+};
+
+class TPortoApi {
+#ifdef __linux__
+    friend class TAsyncWaiter;
+#endif
+private:
+    int Fd = -1;
+    int Timeout = DEFAULT_TIMEOUT;
+    int DiskTimeout = DEFAULT_DISK_TIMEOUT;
+    bool AutoReconnect = true;
+
+    EError LastError = EError::Success;
+    TString LastErrorMsg;
+
+    /*
+     * These keep last request and response. Method might return
+     * pointers to Rsp innards -> pointers valid until next call.
+     */
+    TPortoRequest Req;
+    TPortoResponse Rsp;
+
+    std::vector<TString> AsyncWaitNames;
+    std::vector<TString> AsyncWaitLabels;
+    int AsyncWaitTimeout = INFINITE_TIMEOUT;
+    TWaitCallback AsyncWaitCallback;
+    bool AsyncWaitOneShot = false;
+
+    EError SetError(const TString &prefix, int _errno) Y_WARN_UNUSED_RESULT;
+
+    EError SetSocketTimeout(int direction, int timeout) Y_WARN_UNUSED_RESULT;
+
+    EError Send(const TPortoRequest &req) Y_WARN_UNUSED_RESULT;
+
+    EError Recv(TPortoResponse &rsp) Y_WARN_UNUSED_RESULT;
+
+    EError Call(int extra_timeout = 0) Y_WARN_UNUSED_RESULT;
+
+    EError CallWait(TString &result_state, int wait_timeout) Y_WARN_UNUSED_RESULT;
+
+public:
+    TPortoApi() { }
+    ~TPortoApi();
+
+    int GetFd() const {
+        return Fd;
+    }
+
+    bool Connected() const {
+        return Fd >= 0;
+    }
+
+    EError Connect(const char *socket_path = SOCKET_PATH) Y_WARN_UNUSED_RESULT;
+    void Disconnect();
+
+    /* Requires signal(SIGPIPE, SIG_IGN) */
+    void SetAutoReconnect(bool auto_reconnect) {
+        AutoReconnect = auto_reconnect;
+    }
+
+    /* Request and response timeout in seconds */
+    int GetTimeout() const {
+        return Timeout;
+    }
+    EError SetTimeout(int timeout);
+
+    /* Extra timeout for disk operations in seconds */
+    int GetDiskTimeout() const {
+        return DiskTimeout;
+    }
+    EError SetDiskTimeout(int timeout);
+
+    EError Error() const Y_WARN_UNUSED_RESULT {
+        return LastError;
+    }
+
+    EError GetLastError(TString &msg) const Y_WARN_UNUSED_RESULT {
+        msg = LastErrorMsg;
+        return LastError;
+    }
+
+    /* Returns "LastError:(LastErrorMsg)" */
+    TString GetLastError() const Y_WARN_UNUSED_RESULT;
+
+    /* Returns text protobuf */
+    TString GetLastRequest() const {
+        return Req.DebugString();
+    }
+    TString GetLastResponse() const {
+        return Rsp.DebugString();
+    }
+
+    /* To be used for next changed_since */
+    uint64_t ResponseTimestamp() const Y_WARN_UNUSED_RESULT {
+        return Rsp.timestamp();
+    }
+
+    // extra_timeout: 0 - none, -1 - infinite
+    EError Call(const TPortoRequest &req,
+                TPortoResponse &rsp,
+                int extra_timeout = 0) Y_WARN_UNUSED_RESULT;
+
+    EError Call(const TString &req,
+                TString &rsp,
+                int extra_timeout = 0) Y_WARN_UNUSED_RESULT;
+
+    /* System */
+
+    EError GetVersion(TString &tag, TString &revision) Y_WARN_UNUSED_RESULT;
+
+    const TGetSystemResponse *GetSystem();
+
+    EError SetSystem(const TString &key, const TString &val) Y_WARN_UNUSED_RESULT;
+
+    /* Container */
+
+    const TListPropertiesResponse *ListProperties();
+
+    EError ListProperties(TVector<TString> &properties) Y_WARN_UNUSED_RESULT;
+
+    const TListResponse *List(const TString &mask = "");
+
+    EError List(TVector<TString> &names, const TString &mask = "") Y_WARN_UNUSED_RESULT;
+
+    EError Create(const TString &name) Y_WARN_UNUSED_RESULT;
+
+    EError CreateWeakContainer(const TString &name) Y_WARN_UNUSED_RESULT;
+
+    EError Destroy(const TString &name) Y_WARN_UNUSED_RESULT;
+
+    EError Start(const TString &name)Y_WARN_UNUSED_RESULT;
+
+    // stop_timeout: time between SIGTERM and SIGKILL, -1 - default
+    EError Stop(const TString &name, int stop_timeout = -1) Y_WARN_UNUSED_RESULT;
+
+    EError Kill(const TString &name, int sig = 9) Y_WARN_UNUSED_RESULT;
+
+    EError Pause(const TString &name) Y_WARN_UNUSED_RESULT;
+
+    EError Resume(const TString &name) Y_WARN_UNUSED_RESULT;
+
+    EError Respawn(const TString &name) Y_WARN_UNUSED_RESULT;
+
+    // wait_timeout: 0 - nonblock, -1 - infinite
+    EError WaitContainer(const TString &name,
+                         TString &result_state,
+                         int wait_timeout = INFINITE_TIMEOUT) Y_WARN_UNUSED_RESULT;
+
+    EError WaitContainers(const TVector<TString> &names,
+                          TString &result_name,
+                          TString &result_state,
+                          int wait_timeout = INFINITE_TIMEOUT) Y_WARN_UNUSED_RESULT;
+
+    const TWaitResponse *Wait(const TVector<TString> &names,
+                              const TVector<TString> &labels,
+                              int wait_timeout = INFINITE_TIMEOUT) Y_WARN_UNUSED_RESULT;
+
+    EError AsyncWait(const TVector<TString> &names,
+                     const TVector<TString> &labels,
+                     TWaitCallback callbacks,
+                     int wait_timeout = INFINITE_TIMEOUT,
+                     const TString &targetState = "") Y_WARN_UNUSED_RESULT;
+
+    EError StopAsyncWait(const TVector<TString> &names,
+                         const TVector<TString> &labels,
+                         const TString &targetState = "") Y_WARN_UNUSED_RESULT;
+
+    const TGetResponse *Get(const TVector<TString> &names,
+                            const TVector<TString> &properties,
+                            int flags = 0) Y_WARN_UNUSED_RESULT;
+
+    /* Porto v5 api */
+    EError GetContainerSpec(const TString &name, TContainer &container) Y_WARN_UNUSED_RESULT ;
+    EError ListContainersBy(const TListContainersRequest &listContainersRequest, TVector<TContainer> &containers) Y_WARN_UNUSED_RESULT;
+    EError CreateFromSpec(const TContainerSpec &container, TVector<TVolumeSpec> volumes, bool start = false) Y_WARN_UNUSED_RESULT;
+    EError UpdateFromSpec(const TContainerSpec &container) Y_WARN_UNUSED_RESULT;
+
+    EError GetProperty(const TString &name,
+                       const TString &property,
+                       TString &value,
+                       int flags = 0) Y_WARN_UNUSED_RESULT;
+
+    EError GetProperty(const TString &name,
+                       const TString &property,
+                       const TString &index,
+                       TString &value,
+                       int flags = 0) Y_WARN_UNUSED_RESULT {
+        return GetProperty(name, property + "[" + index + "]", value, flags);
+    }
+
+    EError SetProperty(const TString &name,
+                       const TString &property,
+                       const TString &value) Y_WARN_UNUSED_RESULT;
+
+    EError SetProperty(const TString &name,
+                       const TString &property,
+                       const TString &index,
+                       const TString &value) Y_WARN_UNUSED_RESULT {
+        return SetProperty(name, property + "[" + index + "]", value);
+    }
+
+    EError GetInt(const TString &name,
+                  const TString &property,
+                  const TString &index,
+                  uint64_t &value) Y_WARN_UNUSED_RESULT;
+
+    EError GetInt(const TString &name,
+                       const TString &property,
+                       uint64_t &value) Y_WARN_UNUSED_RESULT {
+        return GetInt(name, property, "", value);
+    }
+
+    EError SetInt(const TString &name,
+                  const TString &property,
+                  const TString &index,
+                  uint64_t value) Y_WARN_UNUSED_RESULT;
+
+    EError SetInt(const TString &name,
+                  const TString &property,
+                  uint64_t value) Y_WARN_UNUSED_RESULT {
+        return SetInt(name, property, "", value);
+    }
+
+    EError GetProcMetric(const TVector<TString> &names,
+                         const TString &metric,
+                         TMap<TString, uint64_t> &values);
+
+    EError GetLabel(const TString &name,
+                    const TString &label,
+                    TString &value) Y_WARN_UNUSED_RESULT {
+        return GetProperty(name, "labels", label, value);
+    }
+
+    EError SetLabel(const TString &name,
+                    const TString &label,
+                    const TString &value,
+                    const TString &prev_value = " ") Y_WARN_UNUSED_RESULT;
+
+    EError IncLabel(const TString &name,
+                    const TString &label,
+                    int64_t add,
+                    int64_t &result) Y_WARN_UNUSED_RESULT;
+
+    EError IncLabel(const TString &name,
+                    const TString &label,
+                    int64_t add = 1) Y_WARN_UNUSED_RESULT {
+        int64_t result;
+        return IncLabel(name, label, add, result);
+    }
+
+    EError ConvertPath(const TString &path,
+                       const TString &src_name,
+                       const TString &dst_name,
+                       TString &result_path) Y_WARN_UNUSED_RESULT;
+
+    EError AttachProcess(const TString &name, int pid,
+                         const TString &comm = "") Y_WARN_UNUSED_RESULT;
+
+    EError AttachThread(const TString &name, int pid,
+                        const TString &comm = "") Y_WARN_UNUSED_RESULT;
+
+    EError LocateProcess(int pid,
+                         const TString &comm /* = "" */,
+                         TString &name) Y_WARN_UNUSED_RESULT;
+
+    /* Volume */
+
+    const TListVolumePropertiesResponse *ListVolumeProperties();
+
+    EError ListVolumeProperties(TVector<TString> &properties) Y_WARN_UNUSED_RESULT;
+
+    const TListVolumesResponse *ListVolumes(const TString &path = "",
+                                            const TString &container = "");
+
+    EError ListVolumes(TVector<TString> &paths) Y_WARN_UNUSED_RESULT;
+
+    const TVolumeDescription *GetVolumeDesc(const TString &path);
+
+    /* Porto v5 api */
+    EError ListVolumesBy(const TGetVolumeRequest &getVolumeRequest, TVector<TVolumeSpec> &volumes) Y_WARN_UNUSED_RESULT;
+    EError CreateVolumeFromSpec(const TVolumeSpec &volume, TVolumeSpec &resultSpec) Y_WARN_UNUSED_RESULT;
+
+    const TVolumeSpec *GetVolume(const TString &path);
+
+    const TGetVolumeResponse *GetVolumes(uint64_t changed_since = 0);
+
+    EError CreateVolume(TString &path,
+                        const TMap<TString, TString> &config) Y_WARN_UNUSED_RESULT;
+
+    EError LinkVolume(const TString &path,
+                      const TString &container = "",
+                      const TString &target = "",
+                      bool read_only = false,
+                      bool required = false) Y_WARN_UNUSED_RESULT;
+
+    EError UnlinkVolume(const TString &path,
+                        const TString &container = "",
+                        const TString &target = "***",
+                        bool strict = false) Y_WARN_UNUSED_RESULT;
+
+    EError TuneVolume(const TString &path,
+                      const TMap<TString, TString> &config) Y_WARN_UNUSED_RESULT;
+
+    /* Layer */
+
+    const TListLayersResponse *ListLayers(const TString &place = "",
+                                          const TString &mask = "");
+
+    EError ListLayers(TVector<TString> &layers,
+                      const TString &place = "",
+                      const TString &mask = "") Y_WARN_UNUSED_RESULT;
+
+    EError ImportLayer(const TString &layer,
+                       const TString &tarball,
+                       bool merge = false,
+                       const TString &place = "",
+                       const TString &private_value = "",
+                       bool verboseError = false) Y_WARN_UNUSED_RESULT;
+
+    EError ExportLayer(const TString &volume,
+                       const TString &tarball,
+                       const TString &compress = "") Y_WARN_UNUSED_RESULT;
+
+    EError ReExportLayer(const TString &layer,
+                         const TString &tarball,
+                         const TString &compress = "") Y_WARN_UNUSED_RESULT;
+
+    EError RemoveLayer(const TString &layer,
+                       const TString &place = "",
+                       bool async = false) Y_WARN_UNUSED_RESULT;
+
+    EError GetLayerPrivate(TString &private_value,
+                           const TString &layer,
+                           const TString &place = "") Y_WARN_UNUSED_RESULT;
+
+    EError SetLayerPrivate(const TString &private_value,
+                           const TString &layer,
+                           const TString &place = "") Y_WARN_UNUSED_RESULT;
+
+    /* Docker images */
+
+    EError DockerImageStatus(DockerImage &image,
+                             const TString &name,
+                             const TString &place = "") Y_WARN_UNUSED_RESULT;
+
+    EError ListDockerImages(std::vector<DockerImage> &images,
+                            const TString &place = "",
+                            const TString &mask = "") Y_WARN_UNUSED_RESULT;
+
+    EError PullDockerImage(DockerImage &image,
+                           const TString &name,
+                           const TString &place = "",
+                           const TString &auth_token = "",
+                           const TString &auth_host = "",
+                           const TString &auth_service = "") Y_WARN_UNUSED_RESULT;
+
+    EError RemoveDockerImage(const TString &name,
+                             const TString &place = "");
+
+    /* Storage */
+
+    const TListStoragesResponse *ListStorages(const TString &place = "",
+                                              const TString &mask = "");
+
+    EError ListStorages(TVector<TString> &storages,
+                        const TString &place = "",
+                        const TString &mask = "") Y_WARN_UNUSED_RESULT;
+
+    EError RemoveStorage(const TString &storage,
+                         const TString &place = "") Y_WARN_UNUSED_RESULT;
+
+    EError ImportStorage(const TString &storage,
+                         const TString &archive,
+                         const TString &place = "",
+                         const TString &compression = "",
+                         const TString &private_value = "") Y_WARN_UNUSED_RESULT;
+
+    EError ExportStorage(const TString &storage,
+                         const TString &archive,
+                         const TString &place = "",
+                         const TString &compression = "") Y_WARN_UNUSED_RESULT;
+};
+
+#ifdef __linux__
+class TAsyncWaiter {
+    struct TCallbackData {
+        const TWaitCallback Callback;
+        const TString State;
+    };
+
+    enum class ERequestType {
+        None,
+        Add,
+        Del,
+        Stop,
+    };
+
+    THashMap<TString, TCallbackData> AsyncCallbacks;
+    std::unique_ptr<std::thread> WatchDogThread;
+    std::atomic<uint64_t> CallbacksCount;
+    int EpollFd = -1;
+    TPortoApi Api;
+
+    int Sock, MasterSock;
+    TString ReqCt;
+    TString ReqState;
+    TWaitCallback ReqCallback;
+
+    std::function<void(const TString &error, int ret)> FatalCallback;
+    bool FatalError = false;
+
+    void MainCallback(const TWaitResponse &event);
+    inline TWaitCallback GetMainCallback() {
+        return [this](const TWaitResponse &event) {
+            MainCallback(event);
+        };
+    }
+
+    int Repair();
+    void WatchDog();
+
+    void SendInt(int fd, int value);
+    int RecvInt(int fd);
+
+    void HandleAddRequest();
+    void HandleDelRequest();
+
+    void Fatal(const TString &error, int ret) {
+        FatalError = true;
+        FatalCallback(error, ret);
+    }
+
+    public:
+    TAsyncWaiter(std::function<void(const TString &error, int ret)> fatalCallback);
+    ~TAsyncWaiter();
+
+    int Add(const TString &ct, const TString &state, TWaitCallback callback);
+    int Remove(const TString &ct);
+    uint64_t InvocationCount() const {
+        return CallbacksCount;
+    }
+};
+#endif
+
+} /* namespace Porto */

+ 183 - 0
library/cpp/porto/metrics.cpp

@@ -0,0 +1,183 @@
+#include "metrics.hpp"
+
+#include <util/folder/path.h>
+#include <util/generic/maybe.h>
+#include <util/stream/file.h>
+
+namespace Porto {
+
+TMap<TString, TMetric*> ProcMetrics;
+
+TMetric::TMetric(const TString& name, EMetric metric) {
+    Name = name;
+    Metric = metric;
+    ProcMetrics[name] = this;
+}
+
+void TMetric::ClearValues(const TVector<TString>& names, TMap<TString, uint64_t>& values) const {
+    values.clear();
+
+    for (const auto&name : names)
+        values[name] = 0;
+}
+
+EError TMetric::GetValues(const TVector<TString>& names, TMap<TString, uint64_t>& values, TPortoApi& api) const {
+    ClearValues(names, values);
+
+    int procFd = open("/proc", O_RDONLY | O_CLOEXEC | O_DIRECTORY | O_NOCTTY);
+    TFileHandle procFdHandle(procFd);
+    if (procFd == -1)
+        return EError::Unknown;
+
+    TVector<TString> tids;
+    TidSnapshot(tids);
+
+    auto getResponse = api.Get(names, TVector<TString>{"cgroups[freezer]"});
+
+    if (getResponse == nullptr)
+        return EError::Unknown;
+
+    const auto containersCgroups = GetCtFreezerCgroups(getResponse);
+
+    for (const auto& tid : tids) {
+        const TString tidCgroup = GetFreezerCgroup(procFd, tid);
+        if (tidCgroup == "")
+            continue;
+
+        TMaybe<uint64_t> metricValue;
+
+        for (const auto& keyval : containersCgroups) {
+            const TString& containerCgroup = keyval.second;
+            if (MatchCgroups(tidCgroup, containerCgroup)) {
+                if (!metricValue)
+                    metricValue = GetMetric(procFd, tid);
+                values[keyval.first] += *metricValue;
+            }
+        }
+    }
+
+    return EError::Success;
+}
+
+uint64_t TMetric::GetTidSchedMetricValue(int procFd, const TString& tid, const TString& metricName) const {
+    const TString schedPath = tid + "/sched";
+    try {
+        int fd = openat(procFd, schedPath.c_str(), O_RDONLY | O_CLOEXEC | O_NOCTTY, 0);
+        TFile file(fd);
+        if (!file.IsOpen())
+            return 0ul;
+
+        TIFStream iStream(file);
+        TString line;
+        while (iStream.ReadLine(line)) {
+            auto metricPos = line.find(metricName);
+
+            if (metricPos != TString::npos) {
+                auto valuePos = metricPos;
+
+                while (valuePos < line.size() && !::isdigit(line[valuePos]))
+                    ++valuePos;
+
+                TString value = line.substr(valuePos);
+                if (!value.empty() && IsNumber(value))
+                    return IntFromString<uint64_t, 10>(value);
+            }
+        }
+    }
+    catch(...) {}
+
+    return 0ul;
+}
+
+void TMetric::GetPidTasks(const TString& pid, TVector<TString>& tids) const {
+    TFsPath task("/proc/" + pid + "/task");
+    TVector<TString> rawTids;
+
+    try {
+        task.ListNames(rawTids);
+    }
+    catch(...) {}
+
+    for (const auto& tid : rawTids) {
+            tids.push_back(tid);
+    }
+}
+
+void TMetric::TidSnapshot(TVector<TString>& tids) const {
+    TFsPath proc("/proc");
+    TVector<TString> rawPids;
+
+    try {
+        proc.ListNames(rawPids);
+    }
+    catch(...) {}
+
+    for (const auto& pid : rawPids) {
+        if (IsNumber(pid))
+            GetPidTasks(pid, tids);
+    }
+}
+
+TString TMetric::GetFreezerCgroup(int procFd, const TString& tid) const {
+    const TString cgroupPath = tid + "/cgroup";
+    try {
+        int fd = openat(procFd, cgroupPath.c_str(), O_RDONLY | O_CLOEXEC | O_NOCTTY, 0);
+        TFile file(fd);
+        if (!file.IsOpen())
+            return TString();
+
+        TIFStream iStream(file);
+        TString line;
+
+        while (iStream.ReadLine(line)) {
+            static const TString freezer = ":freezer:";
+            auto freezerPos = line.find(freezer);
+
+            if (freezerPos != TString::npos) {
+                line = line.substr(freezerPos + freezer.size());
+                return line;
+            }
+        }
+    }
+    catch(...){}
+
+    return TString();
+}
+
+TMap<TString, TString> TMetric::GetCtFreezerCgroups(const TGetResponse* response) const {
+    TMap<TString, TString> containersProps;
+
+    for (const auto& ctGetListResponse : response->list()) {
+        for (const auto& keyval : ctGetListResponse.keyval()) {
+            if (!keyval.error()) {
+                TString value = keyval.value();
+                static const TString freezerPath = "/sys/fs/cgroup/freezer";
+
+                if (value.find(freezerPath) != TString::npos)
+                    value = value.substr(freezerPath.size());
+
+                containersProps[ctGetListResponse.name()] = value;
+            }
+        }
+    }
+
+    return containersProps;
+}
+
+bool TMetric::MatchCgroups(const TString& tidCgroup, const TString& ctCgroup) const {
+    if (tidCgroup.size() <= ctCgroup.size())
+        return tidCgroup == ctCgroup;
+    return ctCgroup ==  tidCgroup.substr(0, ctCgroup.size()) && tidCgroup[ctCgroup.size()] == '/';
+}
+
+class TCtxsw : public TMetric {
+public:
+    TCtxsw() : TMetric(M_CTXSW, EMetric::CTXSW)
+    {}
+
+    uint64_t GetMetric(int procFd, const TString& tid) const override {
+        return GetTidSchedMetricValue(procFd, tid, "nr_switches");
+    }
+} static Ctxsw;
+
+} /* namespace Porto */

+ 50 - 0
library/cpp/porto/metrics.hpp

@@ -0,0 +1,50 @@
+#pragma once
+
+#include "libporto.hpp"
+
+#include <util/generic/map.h>
+#include <util/generic/vector.h>
+#include <util/string/cast.h>
+#include <util/string/type.h>
+
+#include <library/cpp/porto/proto/rpc.pb.h>
+namespace Porto {
+
+constexpr const char *M_CTXSW = "ctxsw";
+
+enum class EMetric {
+        NONE,
+        CTXSW,
+};
+
+class TMetric {
+public:
+    TString Name;
+    EMetric Metric;
+
+    TMetric(const TString& name, EMetric metric);
+
+    void ClearValues(const TVector<TString>& names, TMap<TString, uint64_t>& values) const;
+    EError GetValues(const TVector<TString>& names, TMap<TString, uint64_t>& values, TPortoApi& api) const;
+
+    // Returns value of metric from /proc/tid/sched for some tid
+    uint64_t GetTidSchedMetricValue(int procFd, const TString& tid, const TString& metricName) const;
+
+    void TidSnapshot(TVector<TString>& tids) const;
+    void GetPidTasks(const TString& pid, TVector<TString>& tids) const;
+
+    // Returns freezer cgroup from /proc/tid/cgroup
+    TString GetFreezerCgroup(int procFd, const TString& tid) const;
+
+    // Resurns clean cgroup[freezer] for containers names
+    TMap<TString, TString> GetCtFreezerCgroups(const TGetResponse* response) const;
+
+    // Verify inclusion of container cgroup in process cgroup
+    bool MatchCgroups(const TString& tidCgroup, const TString& ctCgroup) const;
+
+private:
+    virtual uint64_t GetMetric(int procFd, const TString& tid) const = 0;
+};
+
+extern TMap<TString, TMetric*> ProcMetrics;
+} /* namespace Porto */

+ 1606 - 0
library/cpp/porto/proto/rpc.proto

@@ -0,0 +1,1606 @@
+syntax = "proto2";
+
+option go_package = "github.com/ydb-platform/ydb/library/cpp/porto/proto;myapi";
+
+/*
+   Portod daemon listens on /run/portod.socket unix socket.
+
+   Request:    Varint32 length, TPortoRequest  request
+   Response:   Varint32 length, TPortoResponse response
+
+   Command is defined by optional nested message field.
+   Result will be in nested message with the same name.
+
+   Push notification is send as out of order response.
+
+   Access level depends on client container and uid.
+
+   See defails in porto.md or manpage porto
+
+   TContainer, TVolume and related methods are Porto v5 API.
+*/
+
+package Porto;
+
+// List of error codes
+enum EError {
+    // No errors occured.
+    Success = 0;
+
+    // Unclassified error, usually unexpected syscall fail.
+    Unknown = 1;
+
+    // Unknown method or bad request.
+    InvalidMethod = 2;
+
+    // Container with specified name already exists.
+    ContainerAlreadyExists = 3;
+
+    // Container with specified name doesn't exist.
+    ContainerDoesNotExist = 4;
+
+    // Unknown property specified.
+    InvalidProperty = 5;
+
+    // Unknown data specified.
+    InvalidData = 6;
+
+    // Invalid value of property or data.
+    InvalidValue = 7;
+
+    // Can't perform specified operation in current container state.
+    InvalidState = 8;
+
+    // Permanent faulure: old kernel version, missing feature, configuration, etc.
+    NotSupported = 9;
+
+    // Temporary failure: too much objects, not enough memory, etc.
+    ResourceNotAvailable = 10;
+
+    // Insufficient rights for performing requested operation.
+    Permission = 11;
+
+    // Can't create new volume with specified name, because there is already one.
+    VolumeAlreadyExists = 12;
+
+    // Volume with specified name doesn't exist.
+    VolumeNotFound = 13;
+
+    // Not enough disk space.
+    NoSpace = 14;
+
+    // Object in use.
+    Busy = 15;
+
+    // Volume already linked with container.
+    VolumeAlreadyLinked = 16;
+
+    // Volume not linked with container.
+    VolumeNotLinked = 17;
+
+    // Layer with this name already exists.
+    LayerAlreadyExists = 18;
+
+    // Layer with this name not found.
+    LayerNotFound = 19;
+
+    // Property has no value, data source permanently not available.
+    NoValue = 20;
+
+    // Volume under construction or destruction.
+    VolumeNotReady = 21;
+
+    // Cannot parse or execute command.
+    InvalidCommand = 22;
+
+    // Error code is lost or came from future.
+    LostError = 23;
+
+    // Device node not found.
+    DeviceNotFound = 24;
+
+    // Path does not match restricitons or does not exist.
+    InvalidPath = 25;
+
+    // Wrong or unuseable ip address.
+    InvalidNetworkAddress = 26;
+
+    // Porto in system maintenance state.
+    PortoFrozen = 27;
+
+    // Label with this name is not set.
+    LabelNotFound = 28;
+
+    // Label name does not meet restrictions.
+    InvalidLabel = 29;
+
+    // Errors in tar, on archive extraction
+    HelperError = 30;
+    HelperFatalError = 31;
+
+    // Generic object not found.
+    NotFound = 404;
+
+    // Reserved error code for client library.
+    SocketError = 502;
+
+    // Reserved error code for client library.
+    SocketUnavailable = 503;
+
+    // Reserved error code for client library.
+    SocketTimeout = 504;
+
+    // Portod close client connections on reload
+    PortodReloaded = 505;
+
+    // Reserved error code for taints.
+    Taint = 666;
+
+    // Reserved error codes 700-800 to docker
+    Docker = 700;
+    DockerImageNotFound = 701;
+
+    // Internal error code, not for users.
+    Queued = 1000;
+}
+
+
+message TPortoRequest {
+
+    /* System methods */
+
+    // Get portod version
+    optional TVersionRequest Version = 14;
+
+    // Get portod statistics
+    optional TGetSystemRequest GetSystem = 300;
+
+    // Change portod state (for host root user only)
+    optional TSetSystemRequest SetSystem = 301;
+
+    /* Container methods */
+
+    // Create new container
+    optional TCreateRequest Create = 1;
+
+    // Create new contaienr and auto destroy when client disconnects
+    optional TCreateRequest CreateWeak = 17;
+
+    // Force kill all and destroy container and nested containers
+    optional TDestroyRequest Destroy = 2;
+
+    // List container names in current namespace
+    optional TListRequest List = 3;
+
+    // Start contianer and parents if needed
+    optional TStartRequest Start = 7;
+
+    // Kill all and stop container
+    optional TStopRequest Stop = 8;
+
+    // Freeze execution
+    optional TPauseRequest Pause = 9;
+
+    // Resume execution
+    optional TResumeRequest Resume = 10;
+
+    // Send signal to main process
+    optional TKillRequest Kill = 13;
+
+    // Restart dead container
+    optional TRespawnRequest Respawn = 18;
+
+    // Wait for process finish or change of labels
+    optional TWaitRequest Wait = 16;
+
+    // Subscribe to push notifictaions
+    optional TWaitRequest AsyncWait = 19;
+    optional TWaitRequest StopAsyncWait = 128;
+
+    /* Container properties */
+
+    // List supported container properties
+    optional TListPropertiesRequest ListProperties = 11;
+
+    // Get one property
+    optional TGetPropertyRequest GetProperty = 4;
+
+    // Set one property
+    optional TSetPropertyRequest SetProperty = 5;
+
+    // Deprecated, now data properties are also read-only properties
+    optional TListDataPropertiesRequest ListDataProperties = 12;
+    optional TGetDataPropertyRequest GetDataProperty = 6;
+
+    // Get multiple properties for multiple containers
+    optional TGetRequest Get = 15;
+
+    /* Container API based on TContainer (Porto v5 API) */
+
+    // Create, configure and start container with volumes
+    optional TCreateFromSpecRequest CreateFromSpec = 230;
+
+    // Set multiple container properties
+    optional TUpdateFromSpecRequest UpdateFromSpec = 231;
+
+    // Get multiple properties for multiple containers
+    optional TListContainersRequest ListContainersBy = 232;
+
+    // Modify symlink in container
+    optional TSetSymlinkRequest SetSymlink = 125;
+
+    /* Container labels - user defined key-value */
+
+    // Find containers with labels
+    optional TFindLabelRequest FindLabel = 20;
+
+    // Atomic compare and set for label
+    optional TSetLabelRequest SetLabel = 21;
+
+    // Atomic add and return for counter in label
+    optional TIncLabelRequest IncLabel = 22;
+
+    /* Volume methods */
+
+    optional TListVolumePropertiesRequest ListVolumeProperties = 103;
+
+    // List layers and their properties
+    optional TListVolumesRequest ListVolumes = 107;
+
+    // Create, configure and build volume
+    optional TCreateVolumeRequest CreateVolume = 104;
+
+    // Change volume properties - for now only resize
+    optional TTuneVolumeRequest TuneVolume = 108;
+
+    // Volume API based on TVolume (Porto v5 API)
+    optional TNewVolumeRequest NewVolume = 126;
+    optional TGetVolumeRequest GetVolume = 127;
+
+    // Add link between container and volume
+    optional TLinkVolumeRequest LinkVolume = 105;
+
+    // Same as LinkVolume but fails if target is not supported
+    optional TLinkVolumeRequest LinkVolumeTarget = 120;
+
+    // Del link between container and volume
+    optional TUnlinkVolumeRequest UnlinkVolume = 106;
+
+    // Same as UnlinkVolume but fails if target is not supported
+    optional TUnlinkVolumeRequest UnlinkVolumeTarget = 121;
+
+    /* Layer methods */
+
+    // Import layer from tarball
+    optional TImportLayerRequest ImportLayer = 110;
+
+    // Remove layer
+    optional TRemoveLayerRequest RemoveLayer = 111;
+
+    // List layers
+    optional TListLayersRequest ListLayers = 112;
+
+    // Export volume or layer into tarball
+    optional TExportLayerRequest ExportLayer = 113;
+
+    // Get/set layer private (user defined string)
+    optional TGetLayerPrivateRequest GetLayerPrivate = 114;
+    optional TSetLayerPrivateRequest SetLayerPrivate = 115;
+
+    /* Storage methods */
+
+    // Volume creation creates required storage if missing
+
+    // List storages and meta storages
+    optional TListStoragesRequest ListStorages = 116;
+
+    optional TRemoveStorageRequest RemoveStorage = 117;
+
+    // Import storage from tarball
+    optional TImportStorageRequest ImportStorage = 118;
+
+    // Export storage into tarball
+    optional TExportStorageRequest ExportStorage = 119;
+
+    // Meta storage (bundle for storages and layers)
+
+    optional TMetaStorage CreateMetaStorage = 122;
+    optional TMetaStorage ResizeMetaStorage = 123;
+    optional TMetaStorage RemoveMetaStorage = 124;
+
+    // Convert path between containers
+    optional TConvertPathRequest ConvertPath = 200;
+
+    /* Process methods */
+
+    // Attach process to nested container
+    optional TAttachProcessRequest AttachProcess = 201;
+
+    // Find container for process
+    optional TLocateProcessRequest LocateProcess = 202;
+
+    // Attach one thread to nexted container
+    optional TAttachProcessRequest AttachThread = 203;
+
+    /* Docker images API */
+
+    optional TDockerImageStatusRequest dockerImageStatus = 303;
+    optional TDockerImageListRequest listDockerImages = 304;
+    optional TDockerImagePullRequest pullDockerImage = 305;
+    optional TDockerImageRemoveRequest removeDockerImage = 306;
+}
+
+
+message TPortoResponse {
+    // Actually always set, hack for adding new error codes
+    optional EError error = 1 [ default = LostError ];
+
+    // Human readable comment - must be shown to user as is
+    optional string errorMsg = 2;
+
+    optional uint64 timestamp = 1000;       // for next changed_since
+
+    /* System methods */
+
+    optional TVersionResponse Version = 8;
+
+    optional TGetSystemResponse GetSystem = 300;
+    optional TSetSystemResponse SetSystem = 301;
+
+    /* Container methods */
+
+    optional TListResponse List = 3;
+
+    optional TWaitResponse Wait = 11;
+
+    optional TWaitResponse AsyncWait = 19;
+
+    /* Container properties */
+
+    optional TListPropertiesResponse ListProperties = 6;
+
+    optional TGetPropertyResponse GetProperty = 4;
+
+
+    // Deprecated
+    optional TListDataPropertiesResponse ListDataProperties = 7;
+    optional TGetDataPropertyResponse GetDataProperty = 5;
+
+    optional TGetResponse Get = 10;
+
+    /* Container API based on TContainer (Porto v5 API) */
+
+    optional TListContainersResponse ListContainersBy = 232;
+
+    /* Container Labels */
+
+    optional TFindLabelResponse FindLabel = 20;
+    optional TSetLabelResponse SetLabel = 21;
+    optional TIncLabelResponse IncLabel = 22;
+
+    /* Volume methods */
+
+    optional TListVolumePropertiesResponse ListVolumeProperties = 12;
+
+    optional TListVolumesResponse ListVolumes = 9;
+
+    optional TVolumeDescription CreateVolume = 13;
+
+    optional TNewVolumeResponse NewVolume = 126;
+
+    optional TGetVolumeResponse GetVolume = 127;
+
+    optional TListLayersResponse ListLayers = 14;
+
+    optional TGetLayerPrivateResponse GetLayerPrivate = 16;
+
+    // List storages and meta storages
+    optional TListStoragesResponse ListStorages = 17;
+
+    optional TConvertPathResponse ConvertPath = 15;
+
+    // Process
+    optional TLocateProcessResponse LocateProcess = 18;
+
+    /* Docker images API */
+
+    optional TDockerImageStatusResponse dockerImageStatus = 302;
+	optional TDockerImageListResponse listDockerImages = 303;
+    optional TDockerImagePullResponse pullDockerImage = 304;
+}
+
+
+// Common objects
+
+
+message TStringMap {
+    message TStringMapEntry {
+        optional string key = 1;
+        optional string val = 2;
+    }
+    // TODO replace with map
+    // map<string, string> map = 1;
+    repeated TStringMapEntry map = 1;
+    optional bool merge = 2;        // in, default: replace
+}
+
+
+message TUintMap {
+    message TUintMapEntry {
+        optional string key = 1;
+        optional uint64 val = 2;
+    }
+    // TODO replace with map
+    // map<string, uint64> map = 1;
+    repeated TUintMapEntry map = 1;
+    optional bool merge = 2;        // in, default: replace
+}
+
+
+message TError {
+    optional EError error = 1 [ default = LostError ];
+    optional string msg = 2;
+}
+
+
+message TCred {
+    optional string user = 1;       // requires user or uid or both
+    optional fixed32 uid = 2;
+    optional string group = 3;
+    optional fixed32 gid = 4;
+    repeated fixed32 grp = 5;       // out, supplementary groups
+}
+
+
+message TCapabilities {
+    repeated string cap = 1;
+    optional string hex = 2;        // out
+}
+
+
+message TContainerCommandArgv {
+        repeated string argv = 1;
+}
+
+
+// Container
+
+
+message TContainerEnvVar {
+    optional string name = 1;    //required
+    optional string value = 2;
+    optional bool unset = 3;        // out
+    optional string salt = 4;
+    optional string hash = 5;
+}
+
+message TContainerEnv {
+    repeated TContainerEnvVar var = 1;
+    optional bool merge = 2;        // in, default: replace
+}
+
+
+message TContainerUlimit {
+    optional string type = 1;    //required
+    optional bool unlimited = 2;
+    optional uint64 soft = 3;
+    optional uint64 hard = 4;
+    optional bool inherited = 5;    // out
+}
+
+message TContainerUlimits {
+    repeated TContainerUlimit ulimit = 1;
+    optional bool merge = 2;        // in, default: replace
+}
+
+
+message TContainerControllers {
+    repeated string controller = 1;
+}
+
+
+message TContainerCgroup {
+    optional string controller = 1; //required
+    optional string path = 2;    //required
+    optional bool inherited = 3;
+}
+
+message TContainerCgroups {
+    repeated TContainerCgroup cgroup = 1;
+}
+
+
+message TContainerCpuSet {
+    optional string policy = 1;     // inherit|set|node|reserve|threads|cores
+    optional uint32 arg = 2;        // for node|reserve|threads|cores
+    optional string list = 3;       // for set
+    repeated uint32 cpu = 4;        // for set (used if list isn't set)
+    optional uint32 count = 5;      // out
+    optional string mems = 6;
+}
+
+
+message TContainerBindMount {
+    optional string source = 1;    //required
+    optional string target = 2;    //required
+    repeated string flag = 3;
+}
+
+message TContainerBindMounts {
+    repeated TContainerBindMount bind = 1;
+}
+
+
+message TContainerVolumeLink {
+    optional string volume = 1;    //required
+    optional string target = 2;
+    optional bool required = 3;
+    optional bool read_only = 4;
+}
+
+message TContainerVolumeLinks {
+    repeated TContainerVolumeLink link = 1;
+}
+
+
+message TContainerVolumes {
+    repeated string volume = 1;
+}
+
+
+message TContainerPlace {
+    optional string place = 1;    //required
+    optional string alias = 2;
+}
+
+message TContainerPlaceConfig {
+    repeated TContainerPlace cfg = 1;
+}
+
+
+message TContainerDevice {
+    optional string device = 1;    //required
+    optional string access = 2;    //required
+    optional string path = 3;
+    optional string mode = 4;
+    optional string user = 5;
+    optional string group = 6;
+}
+
+message TContainerDevices {
+    repeated TContainerDevice device = 1;
+    optional bool merge = 2;        // in, default: replace
+}
+
+
+message TContainerNetOption {
+    optional string opt = 1;    //required
+    repeated string arg = 2;
+}
+
+message TContainerNetConfig {
+    repeated TContainerNetOption cfg = 1;
+    optional bool inherited = 2;    // out
+}
+
+
+message TContainerIpLimit {
+    optional string policy = 1;     //required any|none|some
+    repeated string ip = 2;
+}
+
+
+message TContainerIpConfig {
+    message TContainerIp {
+        optional string dev = 1;    //required
+        optional string ip = 2;    //required
+    }
+    repeated TContainerIp cfg = 1;
+}
+
+
+message TVmStat {
+    optional uint64 count = 1;
+    optional uint64 size = 2;
+    optional uint64 max_size = 3;
+    optional uint64 used = 4;
+    optional uint64 max_used = 5;
+    optional uint64 anon = 6;
+    optional uint64 file = 7;
+    optional uint64 shmem = 8;
+    optional uint64 huge = 9;
+    optional uint64 swap = 10;
+    optional uint64 data = 11;
+    optional uint64 stack = 12;
+    optional uint64 code = 13;
+    optional uint64 locked = 14;
+    optional uint64 table = 15;
+}
+
+message TContainerStatus {
+    optional string absolute_name = 1;  // out, "/porto/..."
+    optional string state = 2;          // out
+    optional uint64 id = 3;             // out
+    optional uint32 level = 4;          // out
+    optional string parent = 5;         // out, "/porto/..."
+
+    optional string absolute_namespace = 6;    // out
+
+    optional int32 root_pid = 7;       // out
+    optional int32 exit_status = 8;    // out
+    optional int32 exit_code = 9;      // out
+    optional bool core_dumped = 10;     // out
+    optional TError start_error = 11;   // out
+    optional uint64 time = 12;          // out
+    optional uint64 dead_time = 13;     // out
+
+    optional TCapabilities capabilities_allowed = 14;           // out
+    optional TCapabilities capabilities_ambient_allowed = 15;   // out
+    optional string root_path = 16;     // out, in client namespace
+    optional uint64 stdout_offset = 17; // out
+    optional uint64 stderr_offset = 18; // out
+    optional string std_err = 69; // out
+    optional string std_out = 70; // out
+
+    optional uint64 creation_time = 19; // out
+    optional uint64 start_time = 20;    // out
+    optional uint64 death_time = 21;    // out
+    optional uint64 change_time = 22;   // out
+    optional bool no_changes = 23;      // out, change_time < changed_since
+    optional string extra_properties = 73;
+
+    optional TContainerCgroups cgroups = 24;     // out
+    optional TContainerCpuSet cpu_set_affinity = 25;     // out
+
+    optional uint64 cpu_usage = 26;            // out
+    optional uint64 cpu_usage_system = 27;     // out
+    optional uint64 cpu_wait = 28;             // out
+    optional uint64 cpu_throttled = 29;        // out
+
+    optional uint64 process_count = 30;        // out
+    optional uint64 thread_count = 31;         // out
+
+    optional TUintMap io_read = 32;        // out, bytes
+    optional TUintMap io_write = 33;       // out, bytes
+    optional TUintMap io_ops = 34;         // out, ops
+    optional TUintMap io_read_ops = 341;   // out, ops
+    optional TUintMap io_write_ops = 342;  // out, ops
+    optional TUintMap io_time = 35;        // out, ns
+    optional TUintMap io_pressure = 351;   // out
+
+    optional TUintMap place_usage = 36;
+    optional uint64 memory_usage = 37;         // out, bytes
+
+    optional uint64 memory_guarantee_total = 38;   // out
+
+    optional uint64 memory_limit_total = 39;   // out
+
+    optional uint64 anon_limit_total = 40;
+    optional uint64 anon_usage = 41;           // out, bytes
+    optional double cpu_guarantee_total = 42;
+    optional double cpu_guarantee_bound = 421;
+    optional double cpu_limit_total = 422;
+    optional double cpu_limit_bound = 423;
+
+    optional uint64 cache_usage = 43;          // out, bytes
+
+    optional uint64 hugetlb_usage = 44;        // out, bytes
+    optional uint64 hugetlb_limit = 45;
+
+    optional uint64 minor_faults = 46;         // out
+    optional uint64 major_faults = 47;         // out
+    optional uint64 memory_reclaimed = 48;     // out
+    optional TVmStat virtual_memory = 49;      // out
+
+    optional uint64 shmem_usage = 71;          // out, bytes
+    optional uint64 mlock_usage = 72;          // out, bytes
+
+    optional uint64 oom_kills = 50;            // out
+    optional uint64 oom_kills_total = 51;      // out
+    optional bool oom_killed = 52;             // out
+
+    optional TUintMap net_bytes = 54;          // out
+    optional TUintMap net_packets = 55;        // out
+    optional TUintMap net_drops = 56;          // out
+    optional TUintMap net_overlimits = 57;     // out
+    optional TUintMap net_rx_bytes = 58;       // out
+    optional TUintMap net_rx_packets = 59;     // out
+    optional TUintMap net_rx_drops = 60;       // out
+    optional TUintMap net_tx_bytes = 61;       // out
+    optional TUintMap net_tx_packets = 62;     // out
+    optional TUintMap net_tx_drops = 63;       // out
+
+    optional TContainerVolumeLinks volumes_linked = 64; // out
+    optional TContainerVolumes volumes_owned = 65;
+
+    repeated TError error = 66;   // out
+    repeated TError warning = 67; // out
+    repeated TError taint = 68;   // out
+}
+
+message TContainerSpec {
+    optional string name = 1;           // required / in client namespace
+    optional bool weak = 2;
+    optional string private = 3;
+    optional TStringMap labels = 4;
+
+    optional string command = 5;
+    optional TContainerCommandArgv command_argv = 76;
+    optional TContainerEnv env = 6;
+    optional TContainerEnv env_secret = 90;  // in, out hides values
+    optional TContainerUlimits ulimit = 7;
+    optional string core_command = 8;
+
+    optional bool isolate = 9;
+    optional string virt_mode = 10;
+    optional string enable_porto = 11;
+    optional string porto_namespace = 12;
+    optional string cgroupfs = 78;
+    optional bool userns = 79;
+
+    optional uint64 aging_time = 13;
+
+    optional TCred task_cred = 14;
+    optional string user = 15;
+    optional string group = 16;
+
+    optional TCred owner_cred = 17;
+    optional string owner_user = 18;
+    optional string owner_group = 19;
+    optional string owner_containers = 77;
+
+    optional TCapabilities capabilities = 20;
+    optional TCapabilities capabilities_ambient = 21;
+
+    optional string root = 22;          // in parent namespace
+    optional bool root_readonly = 23;
+    optional TContainerBindMounts bind = 24;
+    optional TStringMap symlink = 25;
+    optional TContainerDevices devices = 26;
+    optional TContainerPlaceConfig place = 27;
+    optional TUintMap place_limit = 28;
+
+    optional string cwd = 29;
+    optional string stdin_path = 30;
+    optional string stdout_path = 31;
+    optional string stderr_path = 32;
+    optional uint64 stdout_limit = 33;
+    optional uint32 umask = 34;
+
+    optional bool respawn = 35;
+    optional uint64 respawn_count = 36;
+    optional int64 max_respawns = 37;
+    optional uint64 respawn_delay = 38;
+
+    optional TContainerControllers controllers = 39;
+
+    optional string cpu_policy = 40;   // normal|idle|batch|high|rt
+    optional double cpu_weight = 41;   // 0.01 .. 100
+
+    optional double cpu_guarantee = 42;        // in cores
+    optional double cpu_limit = 43;            // in cores
+    optional double cpu_limit_total = 44;      // deprecated (value moved to TContainerStatus)
+    optional uint64 cpu_period = 45;           // ns
+
+    optional TContainerCpuSet cpu_set = 46;
+
+    optional uint64 thread_limit = 47;
+
+    optional string io_policy = 48;    // none|rt|high|normal|batch|idle
+    optional double io_weight = 49;    // 0.01 .. 100
+
+    optional TUintMap io_limit = 50;         // bps
+    optional TUintMap io_guarantee = 84;     // bps
+    optional TUintMap io_ops_limit = 51;     // iops
+    optional TUintMap io_ops_guarantee = 85; // iops
+
+    optional uint64 memory_guarantee = 52;     // bytes
+
+    optional uint64 memory_limit = 53;         // bytes
+
+    optional uint64 anon_limit = 54;
+    optional uint64 anon_max_usage = 55;
+
+    optional uint64 dirty_limit = 56;
+
+    optional uint64 hugetlb_limit = 57;
+
+    optional bool recharge_on_pgfault = 58;
+    optional bool pressurize_on_death = 59;
+    optional bool anon_only = 60;
+
+    optional int32 oom_score_adj = 61;         // -1000 .. +1000
+    optional bool oom_is_fatal = 62;
+
+    optional TContainerNetConfig net = 63;
+    optional TContainerIpLimit ip_limit = 64;
+    optional TContainerIpConfig ip = 65;
+    optional TContainerIpConfig default_gw = 66;
+    optional string hostname = 67;
+    optional string resolv_conf = 68;
+    optional string etc_hosts = 69;
+    optional TStringMap sysctl = 70;
+    optional TUintMap net_guarantee = 71;      // bytes per second
+    optional TUintMap net_limit = 72;          // bytes per second
+    optional TUintMap net_rx_limit = 73;       // bytes per second
+
+    optional TContainerVolumes volumes_required = 75;
+}
+
+message TContainer {
+    optional TContainerSpec spec = 1;    //required
+    optional TContainerStatus status = 2;
+    optional TError error = 3;
+}
+
+
+// Volumes
+
+message TVolumeDescription {
+    required string path = 1;           // path in client namespace
+    map<string, string> properties = 2;
+    repeated string containers = 3;     // linked containers (legacy)
+    repeated TVolumeLink links = 4;     // linked containers with details
+
+    optional uint64 change_time = 5;    // sec since epoch
+    optional bool no_changes = 6;       // change_time < changed_since
+}
+
+
+message TVolumeLink {
+    optional string container = 1;
+    optional string target = 2;         // absolute path in container, default: anon
+    optional bool required = 3;         // container cannot work without it
+    optional bool read_only = 4;
+    optional string host_target = 5;    // out, absolute path in host
+    optional bool container_root = 6;   // in, set container root
+    optional bool container_cwd = 7;    // in, set container cwd
+}
+
+message TVolumeResource {
+    optional uint64 limit = 1;          // bytes or inodes
+    optional uint64 guarantee = 2;      // bytes or inodes
+    optional uint64 usage = 3;          // out, bytes or inodes
+    optional uint64 available = 4;      // out, bytes or inodes
+}
+
+message TVolumeDirectory {
+    optional string path = 1;           // relative path in volume
+    optional TCred cred = 2;            // default: volume cred
+    optional fixed32 permissions = 3;   // default: volume permissions
+}
+
+message TVolumeSymlink {
+    optional string path = 1;           // relative path in volume
+    optional string target_path = 2;
+}
+
+message TVolumeShare {
+    optional string path = 1;           // relative path in volume
+    optional string origin_path = 2;    // absolute path to origin
+    optional bool cow = 3;              // default: mutable share
+}
+
+// Structured Volume description (Porto V5 API)
+
+message TVolumeSpec {
+    optional string path = 1;               // path in container, default: auto
+    optional string container = 2;          // defines root for paths, default: self (client container)
+    repeated TVolumeLink links = 3;         // initial links, default: anon link to self
+
+    optional string id = 4;                 // out
+    optional string state = 5;              // out
+
+    optional string private_value = 6;      // at most 4096 bytes
+
+    optional string device_name = 7;        // out
+
+    optional string backend = 10;           // default: auto
+    optional string place = 11;             // path in host or alias, default from client container
+    optional string storage = 12;           // persistent storage, path or name, default: non-persistent
+    repeated string layers = 13;            // name or path
+    optional bool read_only = 14;
+
+    // defines root directory user, group and permissions
+    optional TCred cred = 20;               // default: self task cred
+    optional fixed32 permissions = 21;      // default: 0775
+
+    optional TVolumeResource space = 22;
+    optional TVolumeResource inodes = 23;
+
+    optional TCred owner = 30;              // default: self owner
+    optional string owner_container = 31;   // default: self
+    optional string place_key = 32;         // out, key for place_limit
+    optional string creator = 33;           // out
+    optional bool auto_path = 34;           // out
+    optional uint32 device_index = 35;      // out
+    optional uint64 build_time = 37;        // out, sec since epoch
+
+    // customization at creation
+    repeated TVolumeDirectory directories = 40; // in
+    repeated TVolumeSymlink symlinks = 41;      // in
+    repeated TVolumeShare shares = 42;          // in
+
+    optional uint64 change_time = 50;       // out, sec since epoch
+    optional bool no_changes = 51;          // out, change_time < changed_since
+}
+
+
+message TLayer {
+    optional string name = 1;           // name or meta/name
+    optional string owner_user = 2;
+    optional string owner_group = 3;
+    optional uint64 last_usage = 4;     // out, sec since last usage
+    optional string private_value = 5;
+}
+
+
+message TStorage {
+    optional string name = 1;           // name or meta/name
+    optional string owner_user = 2;
+    optional string owner_group = 3;
+    optional uint64 last_usage = 4;     // out, sec since last usage
+    optional string private_value = 5;
+}
+
+
+message TMetaStorage {
+    optional string name = 1;
+    optional string place = 2;
+    optional string private_value = 3;
+    optional uint64 space_limit = 4;        // bytes
+    optional uint64 inode_limit = 5;        // inodes
+
+    optional uint64 space_used = 6;         // out, bytes
+    optional uint64 space_available = 7;    // out, bytes
+    optional uint64 inode_used = 8;         // out, inodes
+    optional uint64 inode_available = 9;    // out, inodes
+    optional string owner_user = 10;        // out
+    optional string owner_group = 11;       // out
+    optional uint64 last_usage = 12;        // out, sec since last usage
+}
+
+
+// COMMANDS
+
+// System
+
+// Get porto version
+message TVersionRequest {
+}
+
+message TVersionResponse {
+    optional string tag = 1;
+    optional string revision = 2;
+}
+
+
+// Get porto statistics
+message TGetSystemRequest {
+}
+
+message TGetSystemResponse {
+    optional string porto_version = 1;
+    optional string porto_revision = 2;
+    optional string kernel_version = 3;
+
+    optional fixed64 errors = 4;
+    optional fixed64 warnings = 5;
+    optional fixed64 porto_starts = 6;
+    optional fixed64 porto_uptime = 7;
+    optional fixed64 master_uptime = 8;
+    optional fixed64 taints = 9;
+
+    optional bool frozen = 10;
+    optional bool verbose = 100;
+    optional bool debug = 101;
+    optional fixed64 log_lines = 102;
+    optional fixed64 log_bytes = 103;
+
+    optional fixed64 stream_rotate_bytes = 104;
+    optional fixed64 stream_rotate_errors = 105;
+
+    optional fixed64 log_lines_lost = 106;
+    optional fixed64 log_bytes_lost = 107;
+    optional fixed64 log_open = 108;
+
+    optional fixed64 container_count = 200;
+    optional fixed64 container_limit = 201;
+    optional fixed64 container_running = 202;
+    optional fixed64 container_created = 203;
+    optional fixed64 container_started = 204;
+    optional fixed64 container_start_failed = 205;
+    optional fixed64 container_oom = 206;
+    optional fixed64 container_buried = 207;
+    optional fixed64 container_lost = 208;
+    optional fixed64 container_tainted = 209;
+
+    optional fixed64 volume_count = 300;
+    optional fixed64 volume_limit = 301;
+    optional fixed64 volume_created = 303;
+    optional fixed64 volume_failed = 304;
+    optional fixed64 volume_links = 305;
+    optional fixed64 volume_links_mounted = 306;
+    optional fixed64 volume_lost = 307;
+
+    optional fixed64 layer_import = 390;
+    optional fixed64 layer_export = 391;
+    optional fixed64 layer_remove = 392;
+
+    optional fixed64 client_count = 400;
+    optional fixed64 client_max = 401;
+    optional fixed64 client_connected = 402;
+
+    optional fixed64 request_queued = 500;
+    optional fixed64 request_completed = 501;
+    optional fixed64 request_failed = 502;
+    optional fixed64 request_threads = 503;
+    optional fixed64 request_longer_1s = 504;
+    optional fixed64 request_longer_3s = 505;
+    optional fixed64 request_longer_30s = 506;
+    optional fixed64 request_longer_5m = 507;
+
+    optional fixed64 fail_system = 600;
+    optional fixed64 fail_invalid_value = 601;
+    optional fixed64 fail_invalid_command = 602;
+    optional fixed64 fail_memory_guarantee = 603;
+    optional fixed64 fail_invalid_netaddr = 604;
+
+    optional fixed64 porto_crash = 666;
+
+    optional fixed64 network_count = 700;
+    optional fixed64 network_created = 701;
+    optional fixed64 network_problems = 702;
+    optional fixed64 network_repairs = 703;
+}
+
+
+// Change porto state
+message TSetSystemRequest {
+    optional bool frozen = 10;
+    optional bool verbose = 100;
+    optional bool debug = 101;
+}
+
+message TSetSystemResponse {
+}
+
+message TCreateFromSpecRequest {
+    optional TContainerSpec container = 1;    //required
+    repeated TVolumeSpec volumes = 2;
+    optional bool start = 3;
+}
+
+message TUpdateFromSpecRequest {
+    optional TContainerSpec container = 1;    //required
+    optional bool start = 2;
+}
+
+message TListContainersFilter {
+    optional string name = 1;    // name or wildcards, default: all
+    optional TStringMap labels = 2;
+    optional uint64 changed_since = 3;  // change_time >= changed_since
+}
+
+message TStreamDumpOptions {
+    optional uint64 stdstream_offset = 2; // default: 0
+    optional uint64 stdstream_limit = 3;  // default: 8Mb
+}
+
+message TListContainersFieldOptions {
+    repeated string properties = 1;       // property names, default: all
+    optional TStreamDumpOptions stdout_options = 2;    // for GetIndexed stdout
+    optional TStreamDumpOptions stderr_options = 3;    // for GetIndexed stderr
+}
+
+message TListContainersRequest {
+    repeated TListContainersFilter filters = 1;
+    optional TListContainersFieldOptions field_options = 2;
+}
+
+message TListContainersResponse {
+    repeated TContainer containers = 1;
+}
+
+// List available properties
+message TListPropertiesRequest {
+}
+
+message TListPropertiesResponse {
+    message TContainerPropertyListEntry {
+        optional string name = 1;
+        optional string desc = 2;
+        optional bool read_only = 3;
+        optional bool dynamic = 4;
+    }
+    repeated TContainerPropertyListEntry list = 1;
+}
+
+
+// deprecated, use ListProperties
+message TListDataPropertiesRequest {
+}
+
+message TListDataPropertiesResponse {
+    message TContainerDataListEntry {
+        optional string name = 1;
+        optional string desc = 2;
+    }
+    repeated TContainerDataListEntry list = 1;
+}
+
+
+// Create stopped container
+message TCreateRequest {
+    optional string name = 1;
+}
+
+
+// Stop and destroy container
+message TDestroyRequest {
+    optional string name = 1;
+}
+
+
+// List container names
+message TListRequest {
+    optional string mask = 1;
+    optional uint64 changed_since = 2;  // change_time >= changed_since
+}
+
+message TListResponse {
+    repeated string name = 1;
+    optional string absolute_namespace = 2;
+}
+
+
+// Read one property
+message TGetPropertyRequest {
+    optional string name = 1;
+    optional string property = 2;
+    // update cached counters
+    optional bool sync = 3;
+    optional bool real = 4;
+}
+
+message TGetPropertyResponse {
+    optional string value = 1;
+}
+
+
+// Alias for GetProperty, deprecated
+message TGetDataPropertyRequest {
+    optional string name = 1;
+    optional string data = 2;
+    // update cached counters
+    optional bool sync = 3;
+    optional bool real = 4;
+}
+
+message TGetDataPropertyResponse {
+    optional string value = 1;
+}
+
+
+// Change one property
+message TSetPropertyRequest {
+    optional string name = 1;
+    optional string property = 2;
+    optional string value = 3;
+}
+
+
+// Get multiple properties/data of many containers with one request
+message TGetRequest {
+    // list of containers or wildcards, "***" - all
+    repeated string name = 1;
+
+    // list of properties/data
+    repeated string variable = 2;
+
+    // do not wait busy containers
+    optional bool nonblock = 3;
+
+    // update cached counters
+    optional bool sync = 4;
+    optional bool real = 5;
+
+    // change_time >= changed_since
+    optional uint64 changed_since = 6;
+}
+
+message TGetResponse {
+    message TContainerGetValueResponse {
+        optional string variable = 1;
+        optional EError error = 2;
+        optional string errorMsg = 3;
+        optional string value = 4;
+    }
+
+    message TContainerGetListResponse {
+        optional string name = 1;
+        repeated TContainerGetValueResponse keyval = 2;
+
+        optional uint64 change_time = 3;
+        optional bool no_changes = 4;   // change_time < changed_since
+    }
+
+    repeated TContainerGetListResponse list = 1;
+}
+
+
+// Start stopped container
+message TStartRequest {
+    optional string name = 1;
+}
+
+
+// Restart dead container
+message TRespawnRequest {
+    optional string name = 1;
+}
+
+
+// Stop dead or running container
+message TStopRequest {
+    optional string name = 1;
+    // Timeout in 1/1000 seconds between SIGTERM and SIGKILL, default 30s
+    optional uint32 timeout_ms = 2;
+}
+
+
+// Freeze running container
+message TPauseRequest {
+    optional string name = 1;
+}
+
+
+// Unfreeze paused container
+message TResumeRequest {
+    optional string name = 1;
+}
+
+
+// Translate filesystem path between containers
+message TConvertPathRequest {
+    optional string path = 1;
+    optional string source = 2;
+    optional string destination = 3;
+}
+
+message TConvertPathResponse {
+    optional string path = 1;
+}
+
+
+// Wait while container(s) is/are in running state
+message TWaitRequest {
+    // list of containers or wildcards, "***" - all
+    repeated string name = 1;
+
+    // timeout in 1/1000 seconds, 0 - nonblock
+    optional uint32 timeout_ms = 2;
+
+    // list of label names or wildcards
+    repeated string label = 3;
+
+    // async wait with target_state works only once
+    optional string target_state = 4;
+}
+
+message TWaitResponse {
+    optional string name = 1;           // container name
+    optional string state = 2;          // container state or "timeout"
+    optional uint64 when = 3;           // unix time stamp in seconds
+    optional string label = 4;
+    optional string value = 5;
+}
+
+
+// Send signal main process in container
+message TKillRequest {
+    optional string name = 1;
+    optional int32 sig = 2;
+}
+
+
+// Move process into container
+message TAttachProcessRequest {
+    optional string name = 1;
+    optional uint32 pid = 2;
+    optional string comm = 3; // ignored if empty
+}
+
+
+// Determine container by pid
+message TLocateProcessRequest {
+    optional uint32 pid = 1;
+    optional string comm = 2; // ignored if empty
+}
+
+message TLocateProcessResponse {
+    optional string name = 1;
+}
+
+
+// Labels
+
+
+message TFindLabelRequest {
+    optional string mask = 1;           // containers name or wildcard
+    optional string state = 2;          // filter by container state
+    optional string label = 3;          // label name or wildcard
+    optional string value = 4;          // filter by label value
+}
+
+message TFindLabelResponse {
+    message TFindLabelEntry {
+        optional string name = 1;
+        optional string state = 2;
+        optional string label = 3;
+        optional string value = 4;
+    }
+    repeated TFindLabelEntry list = 1;
+}
+
+
+message TSetLabelRequest {
+    optional string name = 1;
+    optional string label = 2;
+    optional string value = 3;
+    optional string prev_value = 4;     // fail with Busy if does not match
+    optional string state = 5;          // fail with InvalidState if not match
+}
+
+message TSetLabelResponse {
+    optional string prev_value = 1;
+    optional string state = 2;
+}
+
+
+message TIncLabelRequest {
+    optional string name = 1;
+    optional string label = 2;          // missing label starts from 0
+    optional int64 add = 3 [ default = 1];
+}
+
+message TIncLabelResponse {
+    optional int64 result = 1;
+}
+
+
+message TSetSymlinkRequest {
+    optional string container = 1;
+    optional string symlink = 2;
+    optional string target = 3;
+}
+
+
+// Volumes
+
+
+message TNewVolumeRequest {
+    optional TVolumeSpec volume = 1;
+}
+
+message TNewVolumeResponse {
+    optional TVolumeSpec volume = 1;
+}
+
+
+message TGetVolumeRequest {
+    optional string container = 1;  // get paths in container, default: self (client container)
+    repeated string path = 2;       // volume path in container, default: all
+    optional uint64 changed_since = 3;  // change_time >= changed_since
+    repeated string label = 4;      // labels or wildcards
+}
+
+message TGetVolumeResponse {
+    repeated TVolumeSpec volume = 1;
+}
+
+
+// List available volume properties
+message TListVolumePropertiesRequest {
+}
+
+message TListVolumePropertiesResponse {
+    message TVolumePropertyDescription {
+        optional string name = 1;
+        optional string desc = 2;
+    }
+    repeated TVolumePropertyDescription list = 1;
+}
+
+
+// Create new volume
+// "createVolume" returns TVolumeDescription in "volume"
+message TCreateVolumeRequest {
+    optional string path = 1;
+    map<string, string> properties = 2;
+}
+
+
+message TLinkVolumeRequest {
+    optional string path = 1;
+    optional string container = 2;      // default - self (client container)
+    optional string target = 3;         // path in container, "" - anon
+    optional bool required = 4;         // stop container at fail
+    optional bool read_only = 5;
+}
+
+
+message TUnlinkVolumeRequest {
+    optional string path = 1;
+    optional string container = 2;      // default - self, "***" - all
+    optional bool strict = 3;           // non-lazy umount
+    optional string target = 4;         // path in container, "" - anon, default - "***" - all
+}
+
+
+message TListVolumesRequest {
+    optional string path = 1;
+    optional string container = 2;
+    optional uint64 changed_since = 3;  // change_time >= changed_since
+}
+
+message TListVolumesResponse {
+    repeated TVolumeDescription volumes = 1;
+}
+
+
+message TTuneVolumeRequest {
+    optional string path = 1;
+    map<string, string> properties = 2;
+}
+
+// Layers
+
+
+message TListLayersRequest {
+    optional string place = 1;  // default from client container
+    optional string mask = 2;
+}
+
+message TListLayersResponse {
+    repeated string layer = 1;  // layer names (legacy)
+    repeated TLayer layers = 2; // layer with description
+}
+
+
+message TImportLayerRequest {
+    optional string layer = 1;
+    optional string tarball = 2;
+    optional bool merge = 3;
+    optional string place = 4;
+    optional string private_value = 5;
+    optional string compress = 6;
+    optional bool verbose_error = 7;
+}
+
+
+message TExportLayerRequest {
+    optional string volume = 1;
+    optional string tarball = 2;
+    optional string layer = 3;
+    optional string place = 4;
+    optional string compress = 5;
+}
+
+
+message TRemoveLayerRequest {
+    optional string layer = 1;
+    optional string place = 2;
+    optional bool async = 3;
+}
+
+
+message TGetLayerPrivateRequest {
+    optional string layer = 1;
+    optional string place = 2;
+}
+
+message TGetLayerPrivateResponse {
+    optional string private_value = 1;
+}
+
+
+message TSetLayerPrivateRequest {
+    optional string layer = 1;
+    optional string place = 2;
+    optional string private_value = 3;
+}
+
+
+// Storages
+
+
+message TListStoragesRequest {
+    optional string place = 1;
+    optional string mask = 2; // "name" - storage, "name/" - meta-storage
+}
+
+message TListStoragesResponse {
+    repeated TStorage storages = 1;
+    repeated TMetaStorage meta_storages = 2;
+}
+
+
+message TRemoveStorageRequest {
+    optional string name = 1;
+    optional string place = 2;
+}
+
+
+message TImportStorageRequest {
+    optional string name = 1;
+    optional string tarball = 2;
+    optional string place = 3;
+    optional string private_value = 5;
+    optional string compress = 6;
+}
+
+
+message TExportStorageRequest {
+    optional string name = 1;
+    optional string tarball = 2;
+    optional string place = 3;
+    optional string compress = 4;
+}
+
+
+// Docker images API
+
+
+message TDockerImageConfig {
+    repeated string cmd = 1;
+    repeated string env = 2;
+}
+
+message TDockerImage {
+    required string id = 1;
+    repeated string tags = 2;
+    repeated string digests = 3;
+    repeated string layers = 4;
+    optional uint64 size = 5;
+    optional TDockerImageConfig config = 6;
+}
+
+
+message TDockerImageStatusRequest {
+    required string name = 1;
+    optional string place = 2;
+}
+
+message TDockerImageStatusResponse {
+    optional TDockerImage image = 1;
+}
+
+
+message TDockerImageListRequest {
+    optional string place = 1;
+    optional string mask = 2;
+}
+
+message TDockerImageListResponse {
+    repeated TDockerImage images = 1;
+}
+
+
+message TDockerImagePullRequest {
+    required string name = 1;
+    optional string place = 2;
+    optional string auth_token = 3;
+    optional string auth_path = 4;
+    optional string auth_service = 5;
+}
+
+message TDockerImagePullResponse {
+    optional TDockerImage image = 1;
+}
+
+
+message TDockerImageRemoveRequest {
+    required string name = 1;
+    optional string place = 2;
+}

+ 5 - 0
library/cpp/porto/proto/ya.make

@@ -0,0 +1,5 @@
+PROTO_LIBRARY()
+INCLUDE_TAGS(GO_PROTO)
+SRCS(rpc.proto)
+END()
+

+ 17 - 0
library/cpp/porto/ya.make

@@ -0,0 +1,17 @@
+LIBRARY()
+
+BUILD_ONLY_IF(WARNING WARNING LINUX)
+
+PEERDIR(
+    library/cpp/porto/proto
+    contrib/libs/protobuf
+)
+
+SRCS(
+    libporto.cpp
+    metrics.cpp
+)
+
+END()
+
+RECURSE_FOR_TESTS(ut)

Some files were not shown because too many files changed in this diff